内容简介:这道题目比较有意思,我们可以看到一个变量置换表。通过编写程序进行自动替换,可生成美化后的代码。替换脚本如下:
这道题目比较有意思, Web+Pwn ,用 PHP 写了一个模拟数据进出栈的过程。程序源码经过 enphp 加密,当中有很多字符乱码,而且许多变量名也经过混淆。这里可以直接用 var_export 导出全局变量,或者直接用IDE动态调试。
我们可以看到一个变量置换表。通过编写程序进行自动替换,可生成美化后的代码。替换脚本如下:
<?php include 'index.php'; // index.php为被加密的文件 function replaceString($match){ global $O; $index = -1; if(false !== strpos($match[1], 'x')){ $index = hexdec($match[1]); } else{ $index = $match[1]; } if( function_exists($O[$index]) || class_exists($O[$index]) || defined($O[$index]) ){ return $O[$index]; } else{ return "'".addslashes($O[$index])."'"; } } $encrypto_string = file_get_contents('index.php'); $encrypto_string = preg_replace_callback('/\$GLOBALS[\[\{]O0[\]\}][\[\{](\w{1,15})[\]\}]/','replaceString',$encrypto_string); $decrypto_string = preg_replace_callback('/\$[O0]*[\[\{](\w{2,15})[\]\}]/','replaceString',$encrypto_string); file_put_contents('decrypted.php', $decrypto_string); ?>
然后手动删掉乱码字符,最后生成的代码如下:
<?php error_reporting(E_ALL^E_NOTICE); define('O0', 'O'); require_once 'sandbox.php'; $seed = time(); srand($seed); define(INS_OFFSET,rand(0x0000,0xffff)); $regs = array( 'eax'=>0x0, 'ebp'=>0x0, 'esp'=>0x0, 'eip'=>0x0, ); function aslr(&$O00,$O0O){ $O00 = $O00 + 0x60000000 + INS_OFFSET + 0x001 ; } $func_ = array_flip($func); array_walk($func_,aslr); $plt = array_flip($func_); function handle_data($OOO){ $OO0O=&$GLOBALS{O0}; $O000 = strlen($OOO); $O00O = $O000/0x000004+(0x001*($O000%0x000004)); $O0O0 = str_split($OOO,0x000004); $O0O0[$O00O-0x001] = str_pad($O0O0[$O00O-0x001],0x000004,'\0'); foreach ($O0O0 as $O0OO=>&$OO00){ $OO00 = strrev(bin2hex($OO00)); } return $O0O0; } function gen_canary(){ $O0O00=&$GLOBALS{O0}; $OOOO = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789'; $O0000 = $OOOO[rand(0,strlen($OOOO)-0x001)]; $O000O = $OOOO[rand(0,strlen($OOOO)-0x001)]; $O00O0 = $OOOO[rand(0,strlen($OOOO)-0x001)]; $O00OO = '\0'; return handle_data($O0000.$O000O.$O00O0.$O00OO)[0]; } $canary = gen_canary(); $canarycheck = $canary; function check_canary(){ global $canary; global $canarycheck; if($canary != $canarycheck){ die('emmmmmm...Don\'t attack me!'); } } Class O0OO0{ private $ebp,$stack,$esp; public function __construct($O0OOO,$OO000) { $OO00O=&$GLOBALS{O0}; $this->stack = array(); global $regs; $this->ebp = &$regs['ebp']; $this->esp = &$regs['esp']; $this->ebp = 0xfffe0000 + rand(0x0000,0xffff); global $canary; $this->stack[$this->ebp - 0x4] = &$canary; $this->stack[$this->ebp] = $this->ebp + rand(0x0000,0xffff); $this->esp = $this->ebp - (rand(0x20,0x60)*0x000004); $this->stack[$this->ebp + 0x4] = dechex($O0OOO); if($OO000 != NULL) $this->{'pushdata'}($OO000); } public function pushdata($OO0O0){ $OOO00=&$GLOBALS{O0}; $OO0O0 = handle_data($OO0O0); for($OO0OO=0;$OO0OO<count($OO0O0);$OO0OO++){ $this->stack[$this->esp+($OO0OO*0x000004)] = $OO0O0[$OO0OO]; //no args in my stack haha check_canary(); } } public function recover_data($OOO0O){ $OOOO0=&$GLOBALS{O0}; return hex2bin(strrev($OOO0O)); } public function outputdata(){ $O0000O=&$GLOBALS{O0}; global $regs; echo 'root says: '; while(0x001){ if($this->esp == $this->ebp-0x4) break; $this->{'pop'}('eax'); $OOOOO = $this->{'recover_data'}($regs['eax']); $O00000 = explode('\0',$OOOOO); echo $O00000[0]; if(count($O00000)>0x001){ break; } } } public function ret(){ $O000O0=&$GLOBALS{O0}; $this->esp = $this->ebp; $this->{'pop'}('ebp'); $this->{'pop'}('eip'); $this->{'call'}(); } public function get_data_from_reg($O000OO){ $O00OO0=&$GLOBALS{O0}; global $regs; $O00O00 = $this->{'recover_data'}($regs[$O000OO]); $O00O0O = explode('\0',$O00O00); return $O00O0O[0]; } public function call(){ $O0OO00=&$GLOBALS{O0}; global $regs; global $plt; $O00OOO = hexdec($regs['eip']); if(isset($_REQUEST[$O00OOO])) { $this->{'pop'}('eax'); $O0O000 = (int)$this->{'get_data_from_reg'}('eax'); $O0O00O = array(); for($O0O0O0=0;$O0O0O0<$O0O000;$O0O0O0++){ $this->{'pop'}('eax'); $O0O0OO = $this->{'get_data_from_reg'}('eax'); array_push($O0O00O,$_REQUEST[$O0O0OO]); } call_user_func_array($plt[$O00OOO],$O0O00O); } else { call_user_func($plt[$O00OOO]); } } public function push($O0OO0O){ $O0OOOO=&$GLOBALS{O0}; global $regs; $O0OOO0 = $regs[$O0OO0O]; if( hex2bin(strrev($O0OOO0)) == NULL ) die('data error'); $this->stack[$this->esp] = $O0OOO0; $this->esp -= 0x000004; } public function pop($OO0000){ global $regs; $regs[$OO0000] = $this->stack[$this->esp]; $this->esp += 0x000004; } public function __call($OO000O,$OO00O0){ check_canary(); } }class_alias(O0OO0,stack,0);print_R(O0OO0);print_R(stack); if(isset($_POST['data'])) { $phpinfo_addr = array_search(phpinfo, $plt); $gets = $_POST['data']; $main_stack = new stack($phpinfo_addr, $gets); echo '--------------------output---------------------</br></br>'; $main_stack->{'outputdata'}(); echo '</br></br>------------------phpinfo()------------------</br>'; $main_stack->{'ret'}(); }
接下来我们来分段看这些函数的功能,代码当中还是有很多 0o 字符变量,我会将它们替换成其他有含义的变量名。
首先程序用 $regs 来模拟 eax、ebp、esp、eip 4个寄存器,然后定义了 aslr 函数模拟地址空间随机化,但是这个随机化的过程存在问题,因为用了可预测的变量做随机数种子,这样地址就会被计算出来。 handle_data 函数会对数据进行填充,用 \x00 将其长度填充成4的倍数,然后再以4长度分割。但是这个代码分割方式存在问题,例如我们输入的数据为 123 ,按照正常的逻辑经过处理后,数据应该变成 123\x00
,但是这里的 handle_data 函数却会将其处理成 123\x00\x00\x00\x00\x00
(多了4个 \x00
)。
gen_canary函数用户生成用于防止栈溢出的 canary 字符串,代码如下:
接着通过 O0OO0 类的构造创建栈帧,其过程如下图右边所示:
接着开始将数据压栈,但是这里的 pushdata 方法貌似写的有问题,先进栈数据的地址应该要比后进栈的数据地址高,即 push 中的写法是正确的,但是却没调用。回到压栈操作本身,可以发现程序并未对数据的长度进行判断,这样有可能会导致数据过长而覆盖原有的数据。虽然有 canary 的保护,但是这个 canary 是可以泄露的,我们完全可以绕过。
完成数据压栈之后,开始进行数据出栈操作。程序会将 canary 到栈顶之间的所有数据都弹出去,之后结束弹栈操作。
完成数据出栈操作后,开始模拟 ret 指令,弹出下一条指令的地址并调用。 eip 上存放的是 phpinfo 函数的地址,但是这个地址可以利用前面 pushdata 可以覆盖成我们想要的函数地址。然后根据 REQUEST 请求参数中是否含有这个函数地址,分别进行有参函数、无参函数的调用。
这个时候,我们就可以执行任意函数了。但是从 phpinfo 的信息可以看出程序设置了 disable_functions :
file_get_contents,file_put_contents,fwrite,file,chmod,chown,copy,link,fflush,mkdir,popen,rename,touch,unlink,pcntl_alarm,move_upload_file,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,fsockopen,pfsockopen,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,curl_init,curl_exec,curl_multi_init,curl_multi_exec,dba_open,dba_popen,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,dl,putenv
然而没有禁用 create_function 函数,我们便可以利用该函数实现任意代码执行,具体可以参考: create_function函数如何实现RCE 。但是即便这样,我们也没办法绕过 disable_functions 执行 readflag 程序。
这时,可以把题目给的 dockerfile 拿来跑一下,看下其中开放的服务等等。我们会发现题目环境中开启了默认安装的 php-fpm 服务,这样我们可以通过 unix sock 与 php-fpm 进行通信。
其中,很重要的一点是,当我们使用 unix sock 与 php-fpm 进行通信,应用在 PHP 的配置文件是 php-fpm 独立的 php.ini 文件,而这个文件中默认 disable_functions 限制就少了很多。我们在题目上看到的 disable_functions ,实际上是 apache 自己的 php.ini 配置。这样,我们就可以通过 unix sock 与 php-fpm 进行通信,从而绕过 disable_functions 限制。
当我们通过 unix sock 与 php-fpm 进行通信,又会发现不管向 index.php、sandbox.php 哪个文件请求,都会触发如下代码:
我们可以通过搜索环境中默认安装 PHP 后存在的 PHP 文件,利用他们作为请求文件即可绕过上面代码。
然而实际上, PHP 中并不允许通过 ini_set 函数来设置 disable_functions 的值,即便你设置了也不会生效。
由于 apache 模式下设置的 disable_functions 没有禁用 stream_socket_client、stream_socket_sendto 这两个函数。我们可以利用其进行 SSRF ,与 PHP-FPM 进行通信,最后按照 fastcig 请求格式构造报文即可。在看官方 exploit.php 时,发现其通过 PHP_VALUE 设置了 disable_functions=空
,而实际上 disable_functions 这个选项是PHP加载的时候就确定了,并不能通过 PHP_VALUE 设置生效。
mywebsql
这道题比较简单,直接利用admin/admin就可以登录mywebsql管理界面。然后利用mywebsql3.7的RCE直接写shell,具体参考: https://github.com/eddietcc/CVEnotes/blob/master/MyWebSQL/RCE/readme.md
之后要执行readflag程序,而这个程序会先给你一串表达式,你要快速的告诉他答案,答对即可获得flag。这里我使用如下PHP程序获得flag:
<?php $descriptorspec = array( 0 => array("pipe", "r"), // 标准输入,子进程从此管道中读取数据 1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据 2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件 ); $process = proc_open('/readflag', $descriptorspec, $pipes, $cwd, $env); if (is_resource($process)) { $question = fread($pipes[1],1024); // 获取程序问题 $question = fread($pipes[1],1024); // 获取程序问题 $question = trim($question); var_dump($question); eval('$result = '.$question.';'); // 计算问题结果 fwrite($pipes[0], $result); // 回答程序问题 fclose($pipes[0]); var_dump($result); // $flag = stream_get_contents($pipes[1]);// getflag $flag = fread($pipes[1],1024); $flag = fread($pipes[1],1024); $flag = fread($pipes[1],1024); fclose($pipes[1]); var_dump($flag); $return_value = proc_close($process); echo "command returned $return_value\n"; } ?>
赛后还看到有选手逆向readflag程序,然后bypass ualarm函数的,具体参考 https://www.zhaoj.in/read-5479.html
参考
以上所述就是小编给大家介绍的《2019*CTF之Web部分题解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 2018护网杯-web部分题解
- 2019强网杯Web部分题解
- talent-plan tidb 部分个人题解-week 2
- talent-plan tidb 部分个人题解-week 1
- leetcode题解(动态规划)
- leetcode题解(动态规划)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。