今天看到一個(gè)開(kāi)源項(xiàng)目,叫做 Command2API,感覺(jué)挺有意思的,分享給大家。
起源
關(guān)于這個(gè)項(xiàng)目為什么誕生,原 Repo 有這么一段:
“
以近期 Log4j 的 RCE 舉例,在內(nèi)網(wǎng)的安全測(cè)試中,由于網(wǎng)絡(luò)環(huán)境限制導(dǎo)致沒(méi)有 DNSLog 平臺(tái)可用,這時(shí)候做 Log4j 的漏洞驗(yàn)證就考慮直接查看 LDAP 服務(wù)是否有連接進(jìn)來(lái),但是現(xiàn)成的 JNDI 注入工具開(kāi)啟服務(wù)并沒(méi)有 API 可以直接拉取對(duì)應(yīng)服務(wù)的結(jié)果,這就導(dǎo)致需要人工去查看,很費(fèi)時(shí)間,再加上已經(jīng)寫(xiě)好 BurpSuite 被動(dòng)插件進(jìn)行掃描了,為了節(jié)省時(shí)間就簡(jiǎn)單寫(xiě)了這個(gè)腳本用于獲取 JNDI 工具的執(zhí)行結(jié)果并通過(guò) API 的形式返回,便于插件拉取結(jié)果進(jìn)行漏洞驗(yàn)證。
”
反正大意就是說(shuō),有些命令的執(zhí)行結(jié)果如果能夠通過(guò) HTTP的 API 暴露出來(lái),我們就能更方便地獲取到命令的執(zhí)行結(jié)果,在某些場(chǎng)景下會(huì)非常方便。
所以,這里作者寫(xiě)了這個(gè)項(xiàng)目。
原理
這個(gè)原理其實(shí)非常簡(jiǎn)單,就是用一個(gè) Python 線(xiàn)程開(kāi)啟 Web 服務(wù),一個(gè)線(xiàn)程執(zhí)行命令,通過(guò)全局變量與 Web 服務(wù)共享執(zhí)行命令的結(jié)果。
運(yùn)行
這里我們來(lái)運(yùn)行下看看效果吧。
首先需要下載下項(xiàng)目:
git clone https://github.com/gh0stkey/Command2API.git
然后接著指定想運(yùn)行的命令和 API 運(yùn)行的端口就好了,樣例如下:
python Command2Api.py "執(zhí)行的命令" Web運(yùn)行的端口
“
注意,這里的 python 使用的 Python2,而不是 Python3,因?yàn)樵?xiàng)目引用了一個(gè)包叫 BaseHTTPServer,Python3 是沒(méi)有的。
”
這里我們執(zhí)行一個(gè) ping 命令來(lái)試試:
python Command2Api.py "ping www.baidu.com" 8888
運(yùn)行結(jié)果如下:

可以看到,這里首先輸出了一個(gè)運(yùn)行的地址:
URL: http://HOST:8888/c1IvlLF9
這時(shí)候我們打開(kāi) http://localhost:8888/c1IvlLF9 看下。

可以看到控制臺(tái)結(jié)果就呈現(xiàn)在網(wǎng)頁(yè)里面了。
“
但是這個(gè)頁(yè)面沒(méi)法自動(dòng)刷新,需要點(diǎn)擊刷新來(lái)獲取最新的結(jié)果。
”
介紹完了。
所以,這個(gè)項(xiàng)目在某些情況下還是挺有用的。
比如說(shuō):
- 內(nèi)網(wǎng)安全測(cè)試中,可以用于獲取 JNDI 工具的執(zhí)行結(jié)果并通過(guò)API的形式返回,可以更方便地觀測(cè)執(zhí)行結(jié)果。
- 我們想監(jiān)控或?qū)崟r(shí)獲取某個(gè)命令行程序的輸出結(jié)果,比如 Scrapy 爬蟲(chóng)、比如 Web Server 等等,可以將其暴露出來(lái)。
- 我們想快速分享某個(gè)程序的執(zhí)行結(jié)果,則可以通過(guò)這個(gè)命令配合 Ngrok 生成一個(gè)網(wǎng)站分享出去。
等等。
源碼解析
我們?cè)賮?lái)看看源碼吧,其實(shí)非常簡(jiǎn)單,一共就這些代碼:
import subprocess
import BaseHTTPServer
import SimpleHTTPServer
import cgi
import threading
import sys
import string
import random
l = []
uri = '/' + ''.join(random.sample(string.ascii_letters+string.digits,8))
class thread(threading.Thread):
def __init__(self, threadname, command):
threading.Thread.__init__(self, name='Thread_' + threadname)
self.threadname = int(threadname)
self.command = command
def run(self):
global l
ret = subprocess.Popen(
self.command,
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
for i in iter(ret.stdout.readline, b""):
res = i.decode().strip()
print(res)
l.append(res)
class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
global l
if self.path == uri:
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(l)
if __name__ == '__main__':
# New Thread: Get Command Result
t1 = thread('1', sys.argv[1])
t1.start()
# Webserver
port = int(sys.argv[2])
print("URL: http://HOST:{0}{1}".format(port, uri))
Handler = ServerHandler
httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', port), Handler)
httpd.serve_forever()
可以看到這個(gè)命令就是 Popen 執(zhí)行的,然后通過(guò) PIPE 將結(jié)果捕獲出來(lái)賦值為變量,然后同時(shí)另外一個(gè)線(xiàn)程啟動(dòng)服務(wù)器,將這個(gè)結(jié)果寫(xiě)入到 Response 里面。
就是這么簡(jiǎn)單的代碼,實(shí)現(xiàn)了如此便捷的功能。
優(yōu)化
不過(guò)我看這個(gè)項(xiàng)目還是有很多優(yōu)化空間的,簡(jiǎn)單總結(jié)下:
- 現(xiàn)在支持的是 Python2 而不是 Python3。
- 網(wǎng)頁(yè)結(jié)果不能自動(dòng)刷新。
- 網(wǎng)頁(yè)結(jié)果是一個(gè)列表,和控制臺(tái)的結(jié)果格式不太統(tǒng)一。
- 不能通過(guò) pip 來(lái)安全這個(gè)工具包。
- 輸出結(jié)果的 HOST 可以?xún)?yōu)化一下,直接復(fù)制出來(lái)不好訪問(wèn)。
- 可以配合 Ngrok 將結(jié)果進(jìn)行公開(kāi)暴露。
- 如果能通過(guò)網(wǎng)頁(yè)來(lái)對(duì)命令進(jìn)行交互控制就更好了。
我看看如果有時(shí)間的話(huà),我可以試著將這個(gè)項(xiàng)目改寫(xiě)下并實(shí)現(xiàn)如上的一些優(yōu)化功能哈,到時(shí)候?qū)懲炅税l(fā)出來(lái)。
謝謝閱讀~