In order for a union select statement work, both select statements must return the same number of columns. This is because a union select is concatenating both select statements and returning them as one dataset.
If the first table, Users has 4 columns, then the exploit would look like:
select * from Users where UserName='' union select producs.id,products.name,products.price,products.quantity,products from products-- ' and Password=''"
In this case the first select statement will not return a value because there is not a record in the database where UserName='', where as the 2nd statement would return information from the products table. Although a more likely exploit would be to select out the password's from the Users table in the position of the username so that it will be printed by reader.GetName(i).
Another way of exploiting this issue is providing your own data to be returned:
select * from Users where UserName='' union select 1,'admin','password','ADMINISTRATOR','[email protected]' -- ' and Password=''"
If the data returned by the query is used in a sensitive function, like being able to taint a variable used in fileio or code evaluation.
How do you know how many columns the first query has?
I use burp intruder to iterate over every possibility and filter for non-error'ed requests.
... or you could just use sqlmap