内容简介:这道题目比较有意思,我们可以看到一个变量置换表。通过编写程序进行自动替换,可生成美化后的代码。替换脚本如下:
这道题目比较有意思, 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题解(动态规划)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。