26th Sep 2002 [SBWID-5259]
COMMAND
	IBM Informix Web DataBlade SQL injection via HTML header
SYSTEMS AFFECTED
	Web DataBlade 4.12, IDS 9.20/9.21, Linux 2.2/2.4, SunOS 5.7
PROBLEM
	Simon Lodal says :
	 Auto-decoding HTML entities
	 ============================
	HTML encoded strings are automatically being decoded when  used  in  SQL
	statements. It causes developers to create  code  that  looks  fine  but
	actually contains holes, since the logic is circumvented by WDB.
	Any worthy web/database programmer checks all user  input  before  using
	it in an SQL query. WDB has a function $(WEBUNHTML) which  converts  the
	characters <>"& to their HTML entities. When a  string  has  been
	$(WEBUNHTML)'ed it should thus be safe  to  use  it  in  an  SQL  query,
	provided that you enclose the string in double quotes (there can not  be
	any doublequotes inside the string).
	But somewhere on the path before the SQL query  is  being  executed  the
	HTML  entities  are  actually  decoded  into  their  original  character
	representations. I have not found this documented anywhere, and even  if
	it is documented I  would  consider  it  a  bug,  since  this  "feature"
	certainly breaks the "least surprise" principle, which is  a  bad  thing
	to do in security related areas.
	Example:
	
	<!-- Make inputstr harmless -->
	<?MIVAR NAME=inputstr>$(WEBUNHTML,$inputstr)<?/MIVAR>
	<!-- Build query to insert the checked string -->
	<?MIVAR NAME=qstr>INSERT into mytable VALUES ("$inputstr")<?/MIVAR>
	<!-- Execute query -->
	<?MISQL SQL="$qstr"><?/MISQL>
	
	Besides of being an exampe of just how  ugly  WDB  code  is,  this  code
	looks correct;  it  runs  $inputstr  though  the  $(WEBUNHTML)  function
	before inserting. But the query  will  actually  fail  if  the  original
	$inputstr contained a double quote, and it can  therefore  be  exploited
	to execute other SQL code. The string is HTML decoded  again  somewhere,
	that is, the " is converted back to a real doublequote.
	At first one may think that all the user can  do  is  to  make  a  query
	fail, by inserting just one  quote  somewhere,  and  that  the  attacker
	would have to know the exact query in order to actually make it  succeed
	while being  circumvented.  But  it  is  much  simpler  than  that.  The
	webexplode() function will always be available, and it can  be  used  to
	execute SQL of choice. Since it returns string data  it  can  simply  be
	concatenated to other string data, thus executing any SQL, even  without
	interrupting the original query.
	Proof of concept: Given the code above, the malicious  user  would  have
	to put something like the following into an "inputstr" field in an  HTML
	form and submit it:
	
	" || webexplode("<?MISQL SQL='INSERT INTO sysusers VALUES
	(...)'><?/MISQL>", NULL) || "
	
	This INSERT query writes to  a  sensitive  part  of  the  database,  and
	returns nothing at all. The query  on  the  HTML  page  would  therefore
	succeed; nothing is actually changed in the input that it sees. And  the
	attacker does not even have to know the query that is circumvented.
	 SQL Injection
	 =============
	When a user makes a page request, webdriver executes a query  that  will
	both fetch and process  the  page.  This  query  is  vulnerable  to  SQL
	injection attacks, due to bad filtering/escaping of user input.
	Example:   Request    for    "http://victim.com/site/page.html".    HTTP
	authentication is in use, and a correct user/passwd  has  been  supplied
	(have not tested this without HTTP auth). The webdriver log reports  the
	following query being executed:
	
	SELECT webexplode(object,?::html),req_level FROM wbpages WHERE
	name='page' AND path='/' AND req_level <= 100;
	
	Explanation:  webexplode()  invokes  the  page  engine,   returns   some
	processed HTML. wbpages is the table storing HTML pages,  and  the  rest
	is a breakdown of the request. The path  is  "/"  not  "/site/"  because
	webdriver is configured to operate only under  the  (virtual)  directory
	"/site"; that is it's root directory. The .html extension  is  not  part
	of the query since the extension has already been used in another  query
	to figure out which table to fetch the page from. The value "100" is  my
	personal "user level", which is assigned all users; when not using  HTTP
	auth all users have a value of 0. Each page has  a  corresponding  "page
	level" (req_level), thus the protection scheme is that to access a  page
	with page level 200 you must be authenticated as a user who has  a  user
	level >= 200, or get access denied.
	Webdriver fails to properly escape  quotes  in  input  data.  A  request
	string of "http://victim.com/site/'--/page.html" will modify the  "path"
	part of the query, resulting in the following SQL query being executed:
	
	SELECT webexplode(object,?::html),req_level FROM wbpages WHERE
	name='page' AND path='/'--' and req_level <= 100;
	
	Now we get "http://victim.com/site/page.html",  or  any  other  page  we
	want, regardless of our user level.
	Adding a semicolon raises an error, so  you  can  not  execute  multiple
	queries in one operation, and  so  you  can  only  modify  the  existing
	clauses, or add others that will mostly only limit, not widen, what  you
	get. But that is only until you start using UNION queries,  these  allow
	SQL of choice to be inserted.
	The point is that webdriver simply  expects  to  get  a  processed  page
	(essentially just a string) and an int value back from  the  query.  How
	these values are created does not matter. As long as  the  final  result
	contains exactly one row, having a string type column and  an  int  type
	column, webdriver will return the string part to the user and be happy.
	So the trick is to make the  default  part  of  the  query  (see  above)
	return nothing (no rows), then add another UNION'ed query  that  returns
	the data we actually want.
	The webexplode() function returns data of type  "html",  and  since  all
	text types can be cast'ed to  "html"  is  is  easy  to  create  a  UNION
	select; it can simply return any text type plus an int type. Consider:
	
	http://victim.com/site/' UNION ALL SELECT
	FileToClob('/etc/passwd','server')::html,0 FROM sysusers WHERE username
	= USER --/.html
	
	This will get you:
	
	SELECT webexplode(object,?::html),req_level FROM wbpages WHERE name=''
	AND path='/' UNION ALL SELECT FileToClob('/etc/passwd','server')::html,0
	FROM sysusers WHERE username = USER --' and req_level <= 100;
	
	The  first  part  of  the  query   returns   no   rows   (as   long   as
	http://victim.com/site/.html does not exist). The second part will  read
	/etc/passwd and return it as the HTML page.
	The clause "FROM sysusers WHERE username=USER" is a  dummy;  there  must
	be a FROM clause, and it must produce exactly one row.
	This hole  is  still  not  fully  exploited.  Adding  a  UNION'ed  query
	restricts us to  using  SELECT  statements;  even  though  you  can  use
	function expressions to do file I/O it is still not the  same  as  being
	able to execute INSERT, UPDATE, CREATE, DROP etc. So we go  looking  for
	a way to execute entirely standalone SQL statements ... and we find  the
	immediate solution is the webexplode() function, which is by  definition
	available since we are running Web  DataBlade.  It  takes  as  parameter
	some text and a list of environment variables. The  first  parameter  is
	AppPage code (HTML code  with  embedded  queries  and  ugly  programming
	constructs)  which  is   interpreted   by   webexplode().   webexplode()
	processes HTML code with embbeded SQL.
	The above request are plain GET request which  can  be  typed  into  the
	address bar of a browser. However there is a limit on  the  query  size,
	so we want  to  use  POST  instead.  The  following  retrieves  an  HTML
	formatted list of all database users and  passwords  (may  be  encrypted
	depending on setup); substitute with  any  SQL  (INSERT,  UPDATE,  DROP,
	etc):
	
	> telnet victim.com 80
	Trying x.x.x.x...
	Connected to victim.com.
	Escape character is '^]'.
	POST /site/ HTTP/1.0
	Content-Length: 215
	Content-Type: application/x-www-form-urlencoded
	MIval=/'UNION%20SELECT%20webexplode('<html><body><table><?MISQL%20SQL=%22SELECT%20*%20FROM%20wbusers%22><tr>{<td>$*</td>}</tr><?/MISQL></table></body></html>',''),0%20FROM%20sysusers%20WHERE%20username=USER--/.htmlm
	[ENTER]
	
	 Similar bugs
	 ============
	The query exploited here is only the one used to fetch a page  from  the
	database. If the site is password  protected  you  would  need  a  valid
	login/pass to even get to the point where the page  query  is  executed.
	The HTTP authentication is carried out  by  webdriver,  which  means  it
	makes a query for the provided username and password.  Not  surprisingly
	this query is also buggy. So instead  of  spoofing  the  URL  you  could
	simply add quote  tricks  to  the  username  provided.  There  are  some
	problems with this approach however:
	- I have not found the exact username/password query in any logs, so  it
	is hard to say what exactly the query expects. I have just seen the  log
	emitting errors when putting quotes in the username.
	- Authentication info may be cached depending  on  configuration,  which
	might mean that the query is not executed (not tested).
SOLUTION
	None yet.