Secure Coding
Many software projects are not using secure coding practices, and thus many mistakes are quite common and lead to vulnerabilities.
These can then be used by “hackers” in order to gain access your data, and—worse—the data of your users.
This blog will demonstrate common vulnerabilities, and show methods to prevent them. They will be shown in all sorts of applications—from admin scripts over web applications to iPhone apps anything can become topic, as any of these can impact your security.
Wed Sep 12 14:42:09 UTC 2012
Funny Forkbomb
This shell script is a (single-forking-process) fork bomb in
dash
and busybox sh
, but not in
bash
and ksh
:
#!/bin/sh S=`yes|head -n 32769`;while :;do exec<<S;done $S S
Why? And why exactly 32769?
Actually a neat trick to avoid tempfiles for here-documents,
like bash
uses...
Wed Sep 12 14:28:28 UTC 2012
Exploring the iPhone file system sandbox
There is a little known way to explore the iPhone file system without jailbreaking, at least on iPhone 4 and iOS 5.1.1:
- Take any app with file browser or FTP server functionality.
- Create a relative symbolic link to the file system root in its
Documents folder:
$ ifuse --appid APPID ~/mnt $ cd ~/mnt $ ln -snf ../../../../../.. root
- Open the app (or the FTP client)
- Enjoy!
Note that sandbox restrictions still apply; you will not be able to read another app's data this way.
I also did a brute force search for writable directories outside the app sandbox, and did not find any. I wrote this script for the purpose:
#!/usr/bin/perl use strict; use warnings; use Net::FTP; my $list_regex = qr/ ^ (?<type>d) (?<perms>\S+) \s+ (?<links>\S+) \s+ (?<user>\S+) \s+ (?<group>\S+) \s+ (?<size>\S+) \s+ (?<date>\w\w\w\ .{8}) \s+ (?<name>.*) $ /x; @ARGV == 5 or die "Usage: $0 host port user pass root"; my ($host, $port, $user, $pass, $root) = @ARGV; my $ftp = Net::FTP->new("$host:$port", Debug => 0, Passive => 1) or die "No ftp: $@"; $ftp->login($user, $pass) or die "No user/pass: $ftp->message"; open my $fh, ">", "ls-lR.txt" or warn ">ls-lR.txt: $!"; my @results = (); my @queue = ($root); while(@queue) { my $item = shift @queue; print STDERR "[$item] found @{[scalar @results]} results\n"; $ftp->mkdir($item . "/WRITETEST"); my $list = $ftp->dir($item); print $fh "$item:\n"; for(@$list) { print $fh "$_\n"; /$list_regex/ or next; my $name = $+{name}; next if $name eq '.' or $name eq '..'; if($name eq 'WRITETEST') { push @results, $item; $ftp->rmdir($item . "/WRITETEST"); next; } push @queue, "$item/$name"; } print $fh "\n"; } close $fh; print "$_\n" for @results; $ftp->quit;
UPDATE: This symlink hack has been fixed in iOS
6. I found a new way to create this link; however, apparently due
to an issue in ideviceinstaller
, doing this loses the
Documents content of the app:
$ ideviceinstaller -o uninstall -o remove -o copy=. -a com.dspmobile.dbmeterpro $ unzip -l com.dspmobile.dbmeterpro.ipa $ mkdir -p "Payload/dB Meter Pro.app" $ ln -snf ../../../../../.. "Payload/dB Meter Pro.app/root" $ zip -0y com.dspmobile.dbmeterpro.ipa "Payload/dB Meter Pro.app/root" $ ideviceinstaller -i com.dspmobile.dbmeterpro.ipa $ ifuse --appid com.dspmobile.dbmeterpro ~/mnt $ ln -snf "../dB Meter Pro.app/root" ~/mnt/root $ fusermount -u ~/mnt
Wed Apr 11 15:55:39 UTC 2012
Integer Overflows
A
funny tale from PHP development: apparently, preventing integer
overflows is hard. So, let's try to write code to allocate a 2D
matrix of double
...
unsigned int rows, cols; // ... if(rows < 1 || cols < 1) errx(1, "Invalid size"); double **M = calloc(rows * cols * sizeof(double), 1);
The inherent problem here is that the goal is to detect a miscalculation–by doing calculations. And many attempts failed in the past. So, how to best do this?
Standard Method: pre-verification of each step
if(rows < 1 || cols < 1) errx(1, "Invalid size"); if(rows > SIZE_MAX / (size_t) cols) errx(1, "Integer overflow"); size_t rc = rows * cols; if(rc > SIZE_MAX / sizeof(double)) errx(1, "Integer overflow"); size_t n = rc * sizeof(double); double **M = calloc(rc, 1);
What are we doing there? We basically verify before each
calculation step that the calculation will neither overflow nor
divide by zero. We use the fact that the division operator
/
rounds downwards. Also, an integer division cannot
overflow. So the following equations hold:
rows <= SIZE_MAX / (size_t) cols rows * cols <= (SIZE_MAX / (size_t) cols) * cols rows * cols <= SIZE_MAX - {value between 0 and cols-1} rows * cols <= SIZE_MAX
And thus is proven this step causes no integer overflow.
The problem with this method is that it is tedious and leads to quite complex code.
Interesting Method: pre-verification in a single step
if(SIZE_MAX / (size_t) rows / (size_t) cols / sizeof(double) < 1) errx(1, "Integer overflow"); double **M = calloc((size_t) rows * (size_t) cols * sizeof(double), 1);
So, what is this now? Why does this trick guarantee no overflow? Let's work on this equation too.
Theorems for integer division "/": a >= b => a/c >= b/c Proof: Write a as (a/c) * c + (a%c) with (a%c) between 0 and c-1. Write b as (b/c) * c + (b%c) with (b%c) between 0 and c-1. Then it follows: a >= b (a/c) * c + (a%c) >= (b/c) * c + (b%c) ((a/c) - (b/c)) * c >= (b%c) - (a%c) If the theorem were not true, then (a/c) < (b/c). This means that (a/c) < (b/c) <= -1. We get: -1 * c >= ((a/c) - (b/c)) * c >= (b%c) - (a%c) -c >= (b%c) - (a%c) c <= (a%c) - (b%c) However, this cannot be fulfilled for any (a%c) and (b%c) in the range from 0 to c-1. q.e.d. Furthermore: a < b*c => a/c < b Proof: Let's prove the equivalent relation a/c >= b => a >= b*c Write a as (a/c) * c + (a%c) with (a%c) between 0 and c-1. Then it follows: (a/c) >= b (a/c) * c >= b*c (a/c) * c + (a%c) >= b*c + (a%c) a >= b*c + (a%c) a >= b*c q.e.d. // Let's define: r := (size_t) rows c := (size_t) cols d := sizeof(double) // Assuming no overflow takes place, then we get using the above mentioned theorems: SIZE_MAX >= d*c*r | / r SIZE_MAX / r >= d*c | / c SIZE_MAX / r / c >= d | / d SIZE_MAX / r / c / d >= 1 // Assuming an overflow takes place, then we get using the above mentioned theorems: SIZE_MAX < d*c*r | / r SIZE_MAX / r < d*c | / c SIZE_MAX / r / c < d | / d SIZE_MAX / r / c / d < 1
Therefore, the check above always yields the correct result.
Ridiculous Method: floating point post-verification
What actually happens on overflow? The first bits get cut off. We easily see that the overflown value is at most half the correct value, as cutting off a binary number's first bit always reduces it to less than half. So, we can do this:
if(rows < 1 || cols < 1) errx(1, "Invalid size"); size_t ni = (size_t) rows * (size_t) cols * sizeof(double); float nf = (float) rows * (float) cols * sizeof(double); if(ni < 0.8f * nf) errx(1, "Integer overflow"); double **M = calloc(ni, 1);
We know that on overflow, ni
will be smaller than
0.5
times the correct value. So, as long as our
calculation of nf
is accurate enough so that
0.8f * nf
is between 0.5
times the
correct value, and the correct value itself, this actually
works!
By standard methods, you find that a single calculation step for
float
of addition, multiplication, division, or
rounding an input introduces a relative error of at most
10^-6
, as long as no negative values or denormals are
involved anywhere. How many such errors do we need so that
0.8
becomes 1.0
or 0.5
?
223143 of them. We'll never hit THAT in a buffer size calculation...