├── .gitignore ├── README.md ├── kiwi ├── MANIFEST.in ├── kiwi-report.py ├── kiwi.py ├── kiwi │ ├── __init__.py │ ├── core │ │ ├── __init__.py │ │ ├── analyzer.py │ │ ├── common.py │ │ ├── constant.py │ │ ├── context.py │ │ ├── exception.py │ │ ├── featuremgr.py │ │ ├── filemgr.py │ │ ├── issuemgr.py │ │ └── reporter.py │ ├── data │ │ └── html_report_template.html │ └── ui │ │ ├── __init__.py │ │ ├── cli │ │ ├── __init__.py │ │ └── main.py │ │ └── webui │ │ ├── __init__.py │ │ ├── application.py │ │ ├── report_console.py │ │ ├── static │ │ ├── jquery-3.2.1.min.js │ │ └── kiwi.js │ │ └── templates │ │ └── page.html ├── requirements.txt └── setup.py ├── kiwi_data ├── features │ ├── evals │ │ ├── php_evaluate_funcs.py │ │ └── py_evaluate_funcs.py │ ├── php.feature │ ├── python.feature │ └── raw.feature ├── filemap └── senfiles ├── kiwilime ├── Context.sublime-menu ├── Default.sublime-keymap ├── Default.sublime-mousemap ├── Main.sublime-menu ├── Monokai Extended Kiwilime.tmTheme ├── README.md ├── Side Bar.sublime-menu ├── issuedef │ ├── java │ ├── php │ └── python ├── kiwilime.py ├── kiwilime.sublime-settings └── kiwilime.sublime-syntax └── screenshots ├── db_report.png ├── html_report.png └── run.png /.gitignore: -------------------------------------------------------------------------------- 1 | /tmp/* 2 | *.pyc 3 | test.py 4 | track.md 5 | /kiwi/kiwi.egg-info 6 | /kiwi/dist 7 | /kiwi/build 8 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 1 关于 3 | 4 | `kiwi` 项目是一个源代码安全审计工具。适用于黑客、安全研究人员、安全测试人员,支持多种语言的代码审计工作。 5 | 6 | 代码安全审计工作一般需要2个辅助工具: 7 | 8 | 1. 代码审计(搜索)工具,能够从代码中找到可能存在安全问题的位置,并且生成可视化的报告。 9 | 2. 一个方便阅读代码的工具,能够索引代码实现跳转。例如opengrok、SourceInsight、vim+ctags+cscope 10 | 11 | `kiwi`是一个基于文本的安全源码审计工具,它不会对源代码进行语法解析,而是使用简单的正则表达式方式搜索代码,同时提供了问题确认机制用于减少误报。 12 | 13 | `kiwi`工具可以和`opengrok`工具很好得结合起来,使得扫描报告中的问题可以直接链接到相应的代码。如果经常进行源代码审计工作,强烈建议使用 `kiwi + opengrok` 的解决方案, 14 | 15 | `kiwi`是一个规则和框架完全分离的系统,这样用户可以方便得自定义规则,而不用涉及任何框架层面的修改。 16 | 17 | 在该项目中有3个目录,分别对应3个子项目: 18 | 19 | - kiwi。该目录为`kiwi`工具的主体框架,需要安装到系统中。 20 | - kiwi_data。该目录为`kiwi`的缺省规则目录,放到系统中任意位置即可,用户可在此目录修改、编写自己的搜索规则 21 | - kiwilime。该目录提供了一个`sublime text3`插件,用于和`kiwi`配合使用,可以高亮显示扫描结果,快捷键跳转到代码`sublime text3`打开的代码目录相应代码行数等。 22 | 23 | 注: `opengrok`工具是一个B/S架构的大型源码阅读工具,请参考 [这里](https://github.com/oracle/opengrok) 24 | 25 | ## 1.1 kiwi的优劣 26 | 27 | 目前主流的源码审计工具多采用 **语法解析+插件检测** 的方式实现,即工具会对目标代码做语法分析,生成语法树,然后遍历语法树的每个节点,对每个节点调用所有插件(插件实现检测语法节点是否存在安全漏洞)。 28 | 29 | **语法解析+插件检测** 的这种方式更加精确,例如检测eval函数,正则表达式的方式可能会匹配注释,而语法解析的方式则不会,并且语法解析的方式可以回溯语法节点从而做到“数据流分析”。 30 | 31 | 但是 **语法解析+插件检测** 的实现方式太过复杂,需要为支持的每一种编程语言编写语法解析模块,代价很大,因此商业性的工具非常昂贵;而开源的工具支持的编程语言种类非常少,很难做到跨多种语言审计。 32 | 33 | **语法解析+插件检测** 方式相对于高昂代价来说,带来的收益是有限的。实际上多数商业源码审计工具面向的都是非安全人员,而很多黑客仍在使用 **grep** 来做代码审计。 34 | 35 | 作者认为目前的这些工具不是很适合专业安全人员、黑客,对于源码审计来说,最具有技术含量,最具有创造性的是**检测规则**,专业安全人员在做源码审计的时候**检测规则**并非一成不变,在一次源码审计的过程中会不断发现一些新的 **危险代码** ,需要不断调整**检测规则**来适应新的变化,因此**自定义检测规则**是对于专业安全人员、黑客来说才是最重要的。 36 | 37 | `kiwi`就是这样一个工具,使用它可以很方便得随时更新**检测规则**随时再次进行扫描,`kiwi`虽然在实现原理上落后主流技术,但更加适合专业安全人员。 38 | 39 | 40 | ## 1.2 解决方案选择 41 | 42 | 这里推荐两种解决方案来实现安全源码审计: 43 | 44 | 1. kiwi + opengrok。该方案适合大规模的源码审计,可解决**千万行**级别、**团队合作**的源码审计需求(作者所在公司即采用该方案实现)。 45 | 2. kiwi + sublime text3 + kiwilime。该方案适合个人桌面级使用,解决**万行及以下**、**单人**代码审计需求。 46 | 47 | 48 | # 2 安装 49 | 50 | ## 2.1 kiwi 安装 51 | 52 | 使用一下命令安装kiwi: 53 | 54 | git clone https://github.com/alpha1e0/kiwi.git 55 | cd kiwi/kiwi 56 | python setup.py install 57 | 58 | 59 | ## 2.2 kiwi_data 安装 60 | 61 | `kiwi_data` 无需要安装,放到磁盘中某个目录即可,在扫描的时候通过以下两种方式定位到`kiwi_data`目录: 62 | 63 | 1. 通过 **KIWI_DATA_PATH** 环境变量指定`kiwi_data`目录。(推荐的方式) 64 | 2. 在`kiwi`扫描命令行中通过 *-f/--feature_dir* 参数指定`kiwi_data`目录 65 | 66 | 67 | ## 2.3 kiwilime 安装 68 | 69 | **Step 1.** 70 | 71 | 获取 kiwilime 72 | 73 | git clone https://github.com/alpha1e0/kiwi.git 74 | 75 | **Step 2.** 76 | 77 | 打开 Sublime Text 3 package-directory 78 | 79 | Preferences --> Browse packages 80 | 81 | **Step 3.** 82 | 83 | 将 **kiwilime** 目录整个 copy 到 package-directory 84 | 85 | **kiwilime** 的运行依赖于 [the_platinum_searcher](https://github.com/monochromegane/the_platinum_searcher) 工具,从 [https://github.com/monochromegane/the_platinum_searcher/releases](https://github.com/monochromegane/the_platinum_searcher/releases) 下载编译好的工具,并将其加入到环境变量。 86 | 87 | # 3 kiwi 使用 88 | 89 | `kiwi`在安装后会生成两个 console-script: 90 | - kiwi. kiwi的扫描入口命令 91 | - kiwi-report. 用于查看db类型的扫描报告 92 | 93 | 可使用 *kiwi -h* 查看`kiwi`的帮助信息 94 | 95 | [Kiwi 代码安全扫描] 96 | -------------------------------------------------------------------------------- 97 | usage: kiwi [-h] -t TARGET [-f FEATURE_DIR] [-i FEATURE_IDS [FEATURE_IDS ...]] 98 | [-e EXTENSIONS [EXTENSIONS ...]] [--igexts IGEXTS [IGEXTS ...]] 99 | [--excludes EXCLUDES [EXCLUDES ...]] [-c SCTX] [--ectx ECTX] 100 | [-o OUTPUTS [OUTPUTS ...]] [-v] 101 | 102 | Kiwi. 代码安全审计工具 103 | 104 | optional arguments: 105 | -h, --help show this help message and exit 106 | -t TARGET, --target TARGET 107 | 指定待扫描的目录 108 | -f FEATURE_DIR, --feature_dir FEATURE_DIR 109 | 指定漏洞特征定义目录 110 | -i FEATURE_IDS [FEATURE_IDS ...], --feature_ids FEATURE_IDS [FEATURE_IDS ...] 111 | 指定加载哪些漏洞特征 112 | -e EXTENSIONS [EXTENSIONS ...], --extensions EXTENSIONS [EXTENSIONS ...] 113 | 指定扫描哪些类型文件,例如-e php js则扫描.php .js文件 114 | --igexts IGEXTS [IGEXTS ...] 115 | 指定忽略扫描哪些类型文件,例如--igexts php js则忽略扫描.php .js文件 116 | --excludes EXCLUDES [EXCLUDES ...] 117 | 忽略扫描文件路径包含关键字的文件 118 | -c SCTX, --sctx SCTX 指定扫描结果显示的上下文行数 119 | --ectx ECTX 指定用于评估漏洞所需的上下文信息的文件行数 120 | -o OUTPUTS [OUTPUTS ...], --outputs OUTPUTS [OUTPUTS ...] 121 | 指定输出报告文件,支持.txt/.html/.json/.db 122 | -v, --verbose 详细模式,输出扫描过程信息 123 | 124 | 使用 *kiwi-report -h* 查看`kiwi-report`的帮助信息 125 | 126 | [Kiwi report browser.] 127 | -------------------------------------------------------------------------------- 128 | usage: kiwi-report [-h] [-p PORT] [--ip IP] [-d REPORT_PATH] 129 | 130 | Kiwi. Audit source code for security issuses 131 | 132 | optional arguments: 133 | -h, --help show this help message and exit 134 | -p PORT, --port PORT 指定监听端口,默认为5000 135 | --ip IP 指定监听IP 136 | -d REPORT_PATH, --report_path REPORT_PATH 137 | 指定扫描报告目录 138 | 139 | 140 | ## 3.1 kiwi扫描示例 141 | 142 | 在一次扫描中,需要指定: 143 | 1. 被扫描对象,即代码目录 144 | 2. 使用的扫描规则目录。如果不希望每次都指定规则目录,则可以通过设置环境变量 **KIWI_DATA_PATH** 来永久指定。 145 | 146 | 扫描示例 147 | 148 | kiwi -t /tmp/vulcodes2 -f /home/xxx/kiwi_data -o result.html 149 | kiwi -t /tmp/vulcodes2 -o result.db 150 | 151 | ![扫描截图](https://github.com/alpha1e0/kiwi/raw/master/screenshots/run.png) 152 | 153 | ## 3.2 规则编写 154 | 155 | 可通过查看 **kiwi_data** 目录中的内容来获取规则编写示例。 156 | 157 | `kiwi`的规则定义均使用`yaml`语法编写,在规则编写中涉及以下几个简单概念: 158 | 159 | 1、**规则文件** 160 | 161 | 规则文件是指以 *.feature* 未后缀的文件,记录具体的代码搜索规则定义 162 | 163 | 2、**评估函数** 164 | 评估函数为一个原型固定的python函数,用于后期搜索结果确认。例如搜索到subprocess.check_call(cmd, shell=True),此时可在评估函数中确认参数shell是否为True,从而减小误报 165 | 166 | 3、**map文件** 167 | 168 | `kiwi`工具是跨语言的,因此需要区分不同编程语言的文件;而map文件就是用来解决编程语言区分的,例如.py文件被认为是python脚本。 169 | 170 | 4、**senfiles规则文件** 171 | 172 | 这是一个特殊的规则文件,它的规则仅用于匹配文件名。该规则文件用于帮助安全人员找到敏感文件,例如 xxx_upload.php 173 | 174 | 175 | **kiwi_data** 的目录结构如下: 176 | 177 | kiwi_data 178 | .. features # 目录,存放规则文件 179 | .. .. evals # 目录,存放评估函数 180 | .. .. .. py_evaluate_funcs.py # 文件,评估函数脚本 181 | .. .. python.feature # 文件,规则文件 182 | .. .. java.feature 183 | .. filemap # 文件,map文件 184 | .. senfiles # 文件,senfiles文件 185 | 186 | 187 | ### 3.2.1 编写map文件 188 | 189 | 每当新增加一门编程语言的规则时,需要编辑map文件,例如: 190 | 191 | extensions: 192 | - pattern: "\\.py$" 193 | scope: python 194 | 195 | - pattern: "\\.php\\d+$" 196 | scope: php 197 | 198 | - pattern: "\\.java$" 199 | scope: java 200 | 201 | - pattern: "\\.sh$" 202 | scope: linux-shell 203 | 204 | metainfos: 205 | - pattern: "#!/usr/bin\\w+python" 206 | scope: python 207 | 208 | - pattern: "=0 else 0 56 | e = e if e 0: 41 | self._content = self._file.read(maxlen) 42 | else: 43 | self._content = "" 44 | 45 | # format [line, (start,end)] 46 | self._formated_lines = self._get_formated_lines() 47 | 48 | 49 | @property 50 | def length(self): 51 | return len(self._formated_lines) 52 | 53 | 54 | @property 55 | def filename(self): 56 | return self._filename 57 | 58 | @property 59 | def scope(self): 60 | return self._scope 61 | 62 | 63 | def is_text_file(self): 64 | return True 65 | 66 | 67 | def _get_formated_lines(self): 68 | ''' 69 | 文件内容格式化 70 | 将文本格式化为[line,(start,end)]列表,start/end表示每一行的开始、结束索引 71 | ''' 72 | result = [] 73 | sp = self._content.split("\n") 74 | start = end = 0 75 | 76 | for l in sp: 77 | start = end 78 | end = end + len(l) + 1 79 | result.append([l+"\n",(start, end)]) 80 | 81 | return result 82 | 83 | 84 | def get_context_lines(self, lineno, ctxrange): 85 | ''' 86 | 获取匹配上下文信息 87 | ''' 88 | result = [] 89 | 90 | data_lines_len = len(self._formated_lines) 91 | if lineno>data_lines_len or lineno<0: 92 | return "" 93 | 94 | sidx = lineno - (ctxrange+1) 95 | if sidx<0: sidx=0 96 | eidx = lineno + ctxrange 97 | eidx = eidx if eidx=line[1][0] and match.start() " 128 | u"\n" 129 | u"@{filename}\n" 130 | u"{context}\n") 131 | 132 | return template.format( 133 | id = issue['ID'], 134 | name = issue['name'], 135 | pattern = issue['pattern'], 136 | severity = severity_map[issue['severity']][0].capitalize(), 137 | confidence = confidence_map[issue['confidence']][0].capitalize(), 138 | filename = issue['filename'], 139 | context = self._format_issue_context(issue)) 140 | 141 | 142 | def _report(self): 143 | template = u"{title}\n\n\n{issues}\n\n{statistics}" 144 | 145 | title = u"{banner}\nScaning <{directory}> at {time}".format( 146 | banner = self.banner, 147 | directory = conf.target, 148 | time = self.now) 149 | 150 | issues_content = "-"*80 + "\nFound security issues as follows:\n\n" 151 | for issue in issuemgr: 152 | issues_content = issues_content + self._format_issue(issue) 153 | 154 | statistics = "-"*80 + "\nStatistics information:\n" 155 | sinfo = issuemgr.statistics() 156 | for s in sinfo: 157 | statistics = statistics + \ 158 | "{key}: {value}".format(key=severity_map[s][0].capitalize(), 159 | value=sinfo[s]) + "\n" 160 | 161 | content = template.format( 162 | title = title, 163 | issues = issues_content, 164 | statistics = statistics) 165 | 166 | return content 167 | 168 | 169 | 170 | class ConsoleReporter(TextReporter): 171 | scope = "console" 172 | 173 | def _format_issue_context(self, issue): 174 | result = "" 175 | if not issue['context']: 176 | return result 177 | 178 | largest_lineno = issue['context'][-1][0] 179 | no_fmt = "{0:>" + str(len(str(largest_lineno))) + "}" 180 | 181 | for line in issue['context']: 182 | if line[0] == issue['lineno']: 183 | result = result + Out.Y(no_fmt.format(str(line[0])) + ": " +\ 184 | line[1].rstrip() + "\n") 185 | else: 186 | result = result + no_fmt.format(str(line[0])) + "- " +\ 187 | line[1].rstrip() + "\n" 188 | 189 | return result 190 | 191 | 192 | def _format_issue(self, issue): 193 | template = ( 194 | u"[{id}:{name}]\n" 195 | u" " 196 | u"\n" 197 | u"@{filename}\n" 198 | u"{context}\n") 199 | 200 | return template.format( 201 | id = Out.R(issue['ID']), 202 | name = Out.G(issue['name']), 203 | pattern = Out.Y(issue['pattern']), 204 | severity = severity_map[issue['severity']][0].capitalize(), 205 | confidence = confidence_map[issue['confidence']][0].capitalize(), 206 | filename = Out.B(issue['filename']), 207 | context = self._format_issue_context(issue)) 208 | 209 | 210 | def _report(self): 211 | template = u"\n{title}\n{issues}\n\n{statistics}" 212 | 213 | title = u"Scaning <{directory}> at {time}".format( 214 | directory = Out.R(conf.target), 215 | time = Out.R(self.now)) 216 | 217 | issues_content = \ 218 | Out.Y("-"*80 + "\nFound security issues as follows:\n\n") 219 | 220 | for issue in issuemgr: 221 | issues_content = issues_content + self._format_issue(issue) 222 | 223 | statistics = Out.Y("-"*80 + "\nStatistics information:\n") 224 | sinfo = issuemgr.statistics() 225 | for s in sinfo: 226 | statistics = statistics + \ 227 | "{key}: {value}".format(key=severity_map[s][0].capitalize(), 228 | value=sinfo[s]) + "\n" 229 | 230 | content = template.format( 231 | title = title, 232 | issues = issues_content, 233 | statistics = statistics) 234 | 235 | return content 236 | 237 | 238 | 239 | class HtmlReporter(Reporter): 240 | CONTEXT_LINE_LENGTH = 120 # HTML报告中context最大长度 241 | 242 | def _get_formated_issues(self): 243 | def _get_filelink(filename, scandir, lineno): 244 | opengrok_base = os.getenv("KIWI_OPENGROK_BASE") 245 | if not opengrok_base: 246 | return filename 247 | 248 | scandir_basename = os.path.basename(scandir) 249 | scandir_sp = scandir.split(os.sep) 250 | filename_sp = filename.split(os.sep) 251 | 252 | if len(filename_sp) <= len(scandir_sp): 253 | return filename 254 | else: 255 | rest_filename_sp = os.sep.join(filename_sp[len(scandir_sp):]) 256 | return os.path.join(opengrok_base.rstrip("/"), 257 | scandir_basename, 258 | rest_filename_sp) + "#{0}".format(lineno) 259 | 260 | def _format_ctx_line(line): 261 | ret_line = cgi.escape(line) 262 | if len(ret_line) > self.CONTEXT_LINE_LENGTH: 263 | ret_line = ret_line[:self.CONTEXT_LINE_LENGTH]+"......\n" 264 | return ret_line 265 | 266 | 267 | issues = [] 268 | i = 0 269 | for issue in issuemgr: 270 | new_issue = dict(issue) 271 | 272 | i += 1 273 | new_issue['id'] = i 274 | new_issue['issueid'] = issue['ID'] 275 | new_issue['filelink'] = _get_filelink(issue['filename'], 276 | conf.target, issue['lineno']) 277 | 278 | new_issue['status_class'] = status_map[issue['status']][0] 279 | new_issue['status_prompt'] = status_map[issue['status']][1] 280 | 281 | new_issue['severity_class'] = severity_map[issue['severity']][0] 282 | new_issue['severity_prompt'] = severity_map[issue['severity']][1] 283 | 284 | new_issue['confidence_class'] = \ 285 | confidence_map[issue['confidence']][0] 286 | new_issue['confidence_prompt'] = \ 287 | confidence_map[issue['confidence']][1] 288 | 289 | new_issue['context'] = [(l[0], _format_ctx_line(l[1])) \ 290 | for l in issue['context']] 291 | 292 | issues.append(new_issue) 293 | 294 | return issues 295 | 296 | 297 | def _get_scan_info(self): 298 | scan_info = {} 299 | 300 | scan_info['directory'] = conf.target 301 | scan_info['scan_time'] = time.strftime( 302 | "%Y-%m-%d %H:%M:%S", time.localtime()) 303 | 304 | scan_info['scope_titles'] = [] 305 | scan_info['scope_contents'] = [] 306 | for scope,linenum in filemgr.scope_statistics.iteritems(): 307 | scan_info['scope_titles'].append(scope) 308 | scan_info['scope_contents'].append(linenum) 309 | 310 | scan_info['severity_contents'] = \ 311 | [v for k,v in issuemgr.statistics().iteritems()] 312 | 313 | return scan_info 314 | 315 | 316 | def _report(self): 317 | scan_info = self._get_scan_info() 318 | issues = self._get_formated_issues() 319 | 320 | template_file = os.path.join(conf.pkgpath, "data", 321 | "html_report_template.html") 322 | 323 | with open(template_file) as _file: 324 | template = Template(_file.read().decode("utf-8")) 325 | 326 | return template.render(scaninfo=scan_info, issues=issues) 327 | 328 | 329 | 330 | class JsonReporter(Reporter): 331 | def _report(self): 332 | return json.dumps(issuemgr, indent=2) 333 | 334 | 335 | 336 | class DatabaseReporter(Reporter): 337 | def _report(self): 338 | db = IssueDatabase(self._filename) 339 | 340 | scope_titles = [] 341 | scope_contents = [] 342 | for scope,linenum in filemgr.scope_statistics.iteritems(): 343 | scope_titles.append(scope) 344 | scope_contents.append(linenum) 345 | 346 | db.record_scan_info(conf.target, ",".join(scope_titles), 347 | ",".join([str(x) for x in scope_contents])) 348 | 349 | for issue in issuemgr: 350 | db.add_issue(issue) 351 | 352 | return None 353 | 354 | -------------------------------------------------------------------------------- /kiwi/kiwi/data/html_report_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Kiwi 扫描报告 9 | 10 | 11 | 197 | 198 | 199 | 200 |
201 |
202 |
203 | Kiwi. Scaning {{scaninfo.directory}} at {{scaninfo.scan_time}}
204 |
205 |
206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | {% for sc in scaninfo.severity_contents %} 220 | 221 | {% endfor %} 222 | 223 | 224 |
严重程度致命严重一般提示
漏洞个数{{sc}}
225 | 226 | 227 | 228 | 229 | {% for scope in scaninfo.scope_titles %} 230 | 231 | {% endfor %} 232 | 233 | 234 | 235 | 236 | 237 | {% for sno in scaninfo.scope_contents %} 238 | 239 | {% endfor %} 240 | 241 | 242 |
编程语言{{scope}}
代码行数{{sno}}
243 |
244 | 245 |
246 |
247 | {% for issue in issues %} 248 |
249 |
250 |
251 | ISSUE-{{issue.id}} 252 | {{issue.status_prompt}} 253 | {{issue.severity_prompt}} 254 | {{issue.confidence_prompt}} 255 |
256 |
257 | {{issue.issueid}}: {{issue.name}}
258 |
259 | 匹配关键字: {{issue.pattern}}   260 | 目标文件: {{issue.filename}}
261 | 262 |
263 |
{%- for line in issue.context -%}
264 |                         {%- if line[0]==issue.lineno -%}
265 |                             {{line[0]}}: {{line[1]}}
266 |                         {%- else -%}
267 |                             {{line[0]}}: {{line[1]}}
268 |                         {%- endif -%}
269 |                      {%- endfor -%}
270 |
271 |
272 |
273 | {% endfor %} 274 |
275 |
276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/cli/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/cli/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | 10 | 11 | import os 12 | import sys 13 | import argparse 14 | 15 | from kiwi.core.common import conf 16 | from kiwi.core.common import Out 17 | from kiwi.core.analyzer import Analyzer 18 | from kiwi.core.reporter import get_reporter 19 | from kiwi.core.reporter import ConsoleReporter 20 | 21 | 22 | 23 | class TargetParamParser(argparse.Action): 24 | def __call__(self, parser, namespace, values, option_string=None): 25 | setattr(namespace, self.dest, os.path.abspath(values)) 26 | 27 | 28 | class IDParamParser(argparse.Action): 29 | ''' 30 | fileop模块file类型参数处理器 31 | @remarks: 32 | filePath@fileType参数 处理为 (filePath, fileType) 33 | filePath 处理为 (filePath, None) 34 | ''' 35 | def __call__(self, parser, namespace, values, option_string=None): 36 | new_values = [] 37 | 38 | for value in values: 39 | if value.startswith("@"): 40 | file_name = value[1:] 41 | try: 42 | with open(file_name) as _file: 43 | for line in _file: 44 | line = line.strip() 45 | 46 | if line.startswith("#"): 47 | continue 48 | if not line: 49 | continue 50 | 51 | new_values.append(line) 52 | except IOError: 53 | Out.error("can not open file {0}".format(file_name)) 54 | else: 55 | new_values.append(value) 56 | 57 | setattr(namespace, self.dest, new_values) 58 | 59 | 60 | 61 | def main(): 62 | out = Out() 63 | out.init(u"Kiwi 代码安全扫描") 64 | 65 | parser = argparse.ArgumentParser(description=u"Kiwi. 代码安全审计工具") 66 | 67 | parser.add_argument("-t", "--target", required=True, 68 | action=TargetParamParser, help=u"指定待扫描的目录") 69 | parser.add_argument("-f", "--feature_dir", help=u"指定漏洞特征定义目录") 70 | parser.add_argument("-i", "--feature_ids", nargs="+", action=IDParamParser, 71 | help=u"指定加载哪些漏洞特征") 72 | parser.add_argument("-e", "--extensions", nargs="+", 73 | help=u"指定扫描哪些类型文件,例如-e php js则扫描.php .js文件") 74 | parser.add_argument("--igexts", nargs="+", 75 | help=u"指定忽略扫描哪些类型文件,例如--igexts php js则忽略扫描.php .js文件") 76 | parser.add_argument("--excludes", nargs="+", 77 | help=u"忽略扫描文件路径包含关键字的文件") 78 | parser.add_argument("-c", "--sctx", type=int, default=2, 79 | help=u"指定扫描结果显示的上下文行数") 80 | parser.add_argument("--ectx", type=int, default=10, 81 | help=u"指定用于评估漏洞所需的上下文信息的文件行数") 82 | parser.add_argument("-o", "--outputs", nargs="+", 83 | help=u"指定输出报告文件,支持.txt/.html/.json/.db") 84 | parser.add_argument("-v", "--verbose", action="store_true", 85 | help=u"详细模式,输出扫描过程信息") 86 | 87 | args = parser.parse_args() 88 | 89 | conf.init_args(args) 90 | 91 | out.info(u"kiwi 扫描 {0} ...".format(conf.target, )) 92 | Analyzer().analyze() 93 | 94 | if conf.outputs: 95 | out.info(u"kiwi 生成报告 {0}".format(",".join(conf.outputs))) 96 | for report_name in conf.outputs: 97 | reporter = get_reporter(report_name) 98 | reporter.report() 99 | else: 100 | reporter = ConsoleReporter(None) 101 | reporter.report() 102 | 103 | out.close() 104 | 105 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/webui/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/webui/application.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | 5 | ''' 6 | Kiwi, Security tool for auditing source code 7 | -------------------------------------------------------------------------------- 8 | Copyright (c) 2016 alpha1e0 9 | ''' 10 | 11 | 12 | import os 13 | import json 14 | 15 | from flask import Flask 16 | from flask import request 17 | from flask import render_template 18 | from flask import redirect 19 | from flask import url_for 20 | 21 | from kiwi.core.issuemgr import IssueDatabase 22 | from kiwi.core.constant import status_map, severity_map, confidence_map 23 | from kiwi.core.constant import High, Medium, Low, Info 24 | from kiwi.core.constant import New, Old, Falsep 25 | 26 | 27 | 28 | application = Flask(__name__) 29 | 30 | application.report_path = None 31 | 32 | 33 | 34 | def get_reports(report_path, current_report=None): 35 | reports = [] 36 | 37 | for name in os.listdir(report_path): 38 | if name.endswith(".db"): 39 | reports.append(name[:-3]) 40 | 41 | reports.sort() 42 | 43 | return reports 44 | 45 | 46 | 47 | def get_formated_issues(origin_issues, scan_info): 48 | def _get_filelink(filename, scandir, lineno): 49 | opengrok_base = os.getenv("KIWI_OPENGROK_BASE") 50 | if not opengrok_base: 51 | return filename 52 | 53 | scandir_basename = os.path.basename(scandir) 54 | scandir_sp = scandir.split(os.sep) 55 | filename_sp = filename.split(os.sep) 56 | 57 | if len(filename_sp) <= len(scandir_sp): 58 | return filename 59 | else: 60 | rest_filename_sp = os.sep.join(filename_sp[len(scandir_sp):]) 61 | return os.path.join(opengrok_base.rstrip("/"), 62 | scandir_basename, 63 | rest_filename_sp) + "#{0}".format(lineno) 64 | 65 | 66 | issues = [] 67 | for issue in origin_issues: 68 | new_issue = dict(issue) 69 | 70 | new_issue['id'] = issue['id'] 71 | new_issue['issueid'] = issue['issueid'] 72 | new_issue['filelink'] = _get_filelink(issue['filename'], 73 | scan_info['directory'], issue['lineno']) 74 | 75 | new_issue['context'] = json.loads(issue['context']) 76 | 77 | new_issue['status_class'] = status_map[issue['status']][0] 78 | new_issue['status_prompt'] = status_map[issue['status']][1] 79 | 80 | new_issue['severity_class'] = severity_map[issue['severity']][0] 81 | new_issue['severity_prompt'] = severity_map[issue['severity']][1] 82 | 83 | new_issue['confidence_class'] = confidence_map[issue['confidence']][0] 84 | new_issue['confidence_prompt'] = confidence_map[issue['confidence']][1] 85 | 86 | new_issue['comment'] = issue['comment'] or "" 87 | 88 | issues.append(new_issue) 89 | 90 | return issues 91 | 92 | 93 | def get_scan_info(origin_scan_info, reports, current_report, severity_info, 94 | status_info): 95 | scan_info = {} 96 | scan_info['reports'] = reports 97 | scan_info['selected_report'] = current_report 98 | 99 | scan_info['directory'] = origin_scan_info['directory'] 100 | scan_info['scan_time'] = origin_scan_info['scan_time'] 101 | scan_info['scope_titles'] = origin_scan_info['scope_titles'].split(",") 102 | scan_info['scope_contents'] = origin_scan_info['scope_contents'].split(",") 103 | 104 | scan_info['severity_contents'] = [v for k,v in severity_info.iteritems()] 105 | scan_info['status_contents'] = [v for k,v in status_info.iteritems()] 106 | 107 | return scan_info 108 | 109 | 110 | 111 | @application.route('/') 112 | def index(): 113 | return redirect(url_for('view_report')) 114 | 115 | 116 | @application.route('/view', methods=['GET']) 117 | def view_report(): 118 | try: 119 | current_report = request.args['name'] 120 | except KeyError: 121 | current_report = None 122 | 123 | reports = get_reports(application.report_path) 124 | current_report = current_report or reports[0] 125 | 126 | report_file = os.path.join(application.report_path, current_report) + ".db" 127 | rdb = IssueDatabase(report_file) 128 | 129 | origin_scan_info = rdb.get_scan_info()[-1] 130 | severity_info, status_info = rdb.statistics() 131 | 132 | scan_info = get_scan_info(origin_scan_info, reports, current_report, 133 | severity_info, status_info) 134 | 135 | new_issues, old_issues, falsep_issues = rdb.get_classfied_issues() 136 | issues = get_formated_issues(new_issues+old_issues+falsep_issues, scan_info) 137 | 138 | return render_template('page.html', 139 | scaninfo=scan_info, 140 | issues=issues) 141 | 142 | 143 | @application.route("/modify", methods=['POST']) 144 | def modify(): 145 | try: 146 | report_name = request.form['name'] 147 | issue_id = request.form['id'] 148 | falsep = request.form['falsep'] 149 | comment = request.form['comment'] 150 | except KeyError: 151 | return "Error: missing parameter" 152 | 153 | report_file = os.path.join(application.report_path, report_name) + ".db" 154 | rdb = IssueDatabase(report_file) 155 | 156 | if falsep: 157 | status = Falsep 158 | else: 159 | status = None 160 | 161 | rdb.modify(issue_id, status, comment) 162 | 163 | return redirect(url_for('view_report', name=report_name)) 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/webui/report_console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | 5 | ''' 6 | Kiwi, Security tool for auditing source code 7 | -------------------------------------------------------------------------------- 8 | Copyright (c) 2016 alpha1e0 9 | ''' 10 | 11 | 12 | import os 13 | import argparse 14 | 15 | import eventlet 16 | from eventlet import wsgi 17 | 18 | from kiwi.core.common import Out 19 | from application import application 20 | 21 | 22 | 23 | def main(): 24 | out = Out() 25 | out.init("Kiwi report browser.") 26 | 27 | parser = argparse.ArgumentParser(description="Kiwi. Audit source code" 28 | " for security issuses") 29 | 30 | parser.add_argument("-p", "--port", type=int, 31 | help=u"指定监听端口,默认为5000") 32 | parser.add_argument("--ip", help=u"指定监听IP") 33 | parser.add_argument("-d", "--report_path", help=u"指定扫描报告目录") 34 | 35 | args = parser.parse_args() 36 | 37 | port = args.port or 5000 38 | ip = args.ip or '0.0.0.0' 39 | report_path = args.report_path or os.getenv("KIWI_REPORT_PATH") 40 | 41 | if not report_path: 42 | out.error(u"未指定报告目录,请使用-d/--report_path参数或者" 43 | u"KIWI_REPORT_PATH环境变量指定报告目录") 44 | exit(1) 45 | 46 | application.report_path = os.path.realpath(report_path) 47 | 48 | wsgi.server(eventlet.listen((ip, port)), application) 49 | 50 | 51 | 52 | if __name__ == '__main__': 53 | main() 54 | 55 | -------------------------------------------------------------------------------- /kiwi/kiwi/ui/webui/static/kiwi.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha1e0/kiwi/5215cf7aa7c83c7dde8c0c5e5bbc2d1099a3e93f/kiwi/kiwi/ui/webui/static/kiwi.js -------------------------------------------------------------------------------- /kiwi/kiwi/ui/webui/templates/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Kiwi报告查看器 9 | 10 | 11 | 205 | 206 | 207 | 208 | 221 |
222 |
223 |
224 | Kiwi. Scaning {{scaninfo.directory}} at {{scaninfo.scan_time}}
225 |
226 |
227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | {% for sc in scaninfo.severity_contents %} 241 | 242 | {% endfor %} 243 | 244 | 245 |
严重程度致命严重一般提示
漏洞个数{{sc}}
246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | {% for sc in scaninfo.status_contents %} 259 | 260 | {% endfor %} 261 | 262 | 263 |
漏洞状态新漏洞老漏洞误报
漏洞个数{{sc}}
264 | 265 | 266 | 267 | 268 | {% for scope in scaninfo.scope_titles %} 269 | 270 | {% endfor %} 271 | 272 | 273 | 274 | 275 | 276 | {% for sno in scaninfo.scope_contents %} 277 | 278 | {% endfor %} 279 | 280 | 281 |
编程语言{{scope}}
代码行数{{sno}}
282 |
283 | 284 |
285 |
286 | {% for issue in issues %} 287 |
288 |
289 |
290 | ISSUE-{{issue.id}} 291 | {{issue.status_prompt}} 292 | {{issue.severity_prompt}} 293 | {{issue.confidence_prompt}} 294 |
295 |
296 | {{issue.issueid}}: {{issue.name}}
297 |
298 | 匹配关键字: {{issue.pattern}}   299 | 目标文件: {{issue.filename}}
300 | 301 |
302 |
{%- for line in issue.context -%}
303 |                         {%- if line[0]==issue.lineno -%}
304 |                             {{line[0]}}: {{line[1]}}
305 |                         {%- else -%}
306 |                             {{line[0]}}: {{line[1]}}
307 |                         {%- endif -%}
308 |                      {%- endfor -%}
309 |
310 |
311 |
312 |
313 | {% if issue.status==1 %} 314 | 误报 315 | 非误报 316 | {% else %} 317 | 误报 318 | 非误报 319 | {% endif %} 320 |     备注: 321 | 322 | 323 | 324 |
325 |
326 |
327 | {% endfor %} 328 | 329 |
330 |
331 |
332 | 333 |
334 | 335 | 336 | -------------------------------------------------------------------------------- /kiwi/requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs>=1.4.0 2 | pyyaml>=5.1.1 3 | colorama>=0.3.7 4 | flask>=0.12.2 5 | eventlet>=0.21.0 6 | jinja2>=2.7.3 -------------------------------------------------------------------------------- /kiwi/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | 10 | 11 | from setuptools import setup, find_packages 12 | 13 | 14 | 15 | setup( 16 | name="kiwi", 17 | version="1.1", 18 | 19 | packages = find_packages(), 20 | include_package_data = True, 21 | 22 | install_requires=[ 23 | 'appdirs>=1.4.0', 24 | 'pyyaml>=3.11', 25 | 'colorama>=0.3.7', 26 | 'flask>=0.12.2', 27 | 'eventlet>=0.21.0', 28 | 'jinja2>=2.7.3' 29 | ], 30 | 31 | author="alpha1e0", 32 | author_email="yan.shifm@foxmail.com", 33 | description="", 34 | keywords="security hacking audit", 35 | url="https://github.com/alpha1e0/kiwi", 36 | 37 | entry_points = { 38 | 'console_scripts': [ 39 | 'kiwi = kiwi.ui.cli.main:main', 40 | 'kiwi-report = kiwi.ui.webui.report_console:main' 41 | ], 42 | } 43 | ) -------------------------------------------------------------------------------- /kiwi_data/features/evals/php_evaluate_funcs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | 10 | 11 | from kiwi.core.featuremgr import evaluate 12 | 13 | 14 | @evaluate 15 | def php_file_inclusion_001_evaluate(feature, matchctx): 16 | if matchctx.contains("$"): 17 | return (feature['severity'], feature['confidence']) 18 | else: 19 | return None -------------------------------------------------------------------------------- /kiwi_data/features/evals/py_evaluate_funcs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ''' 5 | Kiwi, Security tool for auditing source code 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | 10 | 11 | from kiwi.core.featuremgr import evaluate 12 | 13 | 14 | 15 | @evaluate 16 | def py_cmd_inject_0002_evaluate(feature, matchctx): 17 | if matchctx.contains("shell=True"): 18 | return (feature['severity'], feature['confidence']) 19 | else: 20 | return None -------------------------------------------------------------------------------- /kiwi_data/features/php.feature: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwi, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # Issue defines for php 9 | #=============================================================================== 10 | 11 | version: 1.0 12 | 13 | scopes: 14 | - php 15 | 16 | 17 | features: 18 | - ID: PHP_CMD_INJ_001 19 | name: "Execute command in shells" 20 | references: [] 21 | severity: High 22 | confidence: Medium 23 | patterns: 24 | - \bbackticks\s*\( 25 | - \bexec\s*\( 26 | - \bexpect_popen\s*\( 27 | - \bpassthru\s*\( 28 | - \bpcntl_exec\s*\( 29 | - \bpopen\s*\( 30 | - \bproc_open\s*\( 31 | - \bshell_exec\s*\( 32 | - \bsystem\s*\( 33 | - \bw32api_invoke_function\s*\( 34 | - \bw32api_register_function\s*\( 35 | #- \bmail\s*\( 36 | - \bmb_send_mail\s*\( 37 | 38 | 39 | - ID: PHP_SQL_INJ_002 40 | name: "Execute SQL command inject" 41 | severity: High 42 | confidence: Medium 43 | references: [] 44 | patterns: 45 | - \bdba_open\s*\( 46 | - \bdba_popen\s*\( 47 | - \bdba_insert\s*\( 48 | - \bdba_fetch\s*\( 49 | - \bdba_delete\s*\( 50 | - \bdbx_query\s*\( 51 | - \bodbc_do\s*\( 52 | - \bodbc_exec\s*\( 53 | - \bodbc_execute\s*\( 54 | - \bdb2_exec\s*\( 55 | - \bdb2_execute\s*\( 56 | - \bfbsql_db_query\s*\( 57 | - \bfbsql_query\s*\( 58 | - \bibase_query\s*\( 59 | - \bibase_execute\s*\( 60 | - \bifx_query\s*\( 61 | - \bifx_do\s*\( 62 | - \bingres_query\s*\( 63 | - \bingres_execute\s*\( 64 | - \bingres_unbuffered_query\s*\( 65 | - \bmsql_db_query\s*\( 66 | - \bmsql_query\s*\( 67 | - \bmsql\s*\( 68 | - \bmssql_query\s*\( 69 | - \bmssql_execute\s*\( 70 | - \bmysql_db_query\s*\( 71 | - \bmysql_query\s*\( 72 | - \bmysql_unbuffered_query\s*\( 73 | - \bmysqli_stmt_execute\s*\( 74 | - \bmysqli_query\s*\( 75 | - \bmysqli_real_query\s*\( 76 | - \bmysqli_master_query\s*\( 77 | - \boci_execute\s*\( 78 | - \bociexecute\s*\( 79 | - \bovrimos_exec\s*\( 80 | - \bovrimos_execute\s*\( 81 | - \bora_do\s*\( 82 | - \bora_exec\s*\( 83 | - \bpg_query\s*\( 84 | - \bpg_send_query\s*\( 85 | - \bpg_send_query_params\s*\( 86 | - \bpg_send_prepare\s*\( 87 | - \bpg_prepare\s*\( 88 | - \bsqlite_open\s*\( 89 | - \bsqlite_popen\s*\( 90 | - \bsqlite_array_query\s*\( 91 | - \barrayQuery\s*\( 92 | - \bsingleQuery\s*\( 93 | - \bsqlite_query\s*\( 94 | - \bsqlite_exec\s*\( 95 | - \bsqlite_single_query\s*\( 96 | - \bsqlite_unbuffered_query\s*\( 97 | - \bsybase_query\s*\( 98 | - \bsybase_unbuffered_query\s*\( 99 | 100 | 101 | - ID: PHP_EVAL_001 102 | name: "Evaluate php code in functions like 'eval'" 103 | severity: High 104 | confidence: Medium 105 | references: [] 106 | patterns: 107 | - \bassert\s*\( 108 | - \bcreate_function\s*\( 109 | - \beval\s*\( 110 | - \bmb_ereg_replace\s*\( 111 | - \bmb_eregi_replace\s*\( 112 | - \bpreg_filter\s*\( 113 | - \bpreg_replace\s*\( 114 | - \bpreg_replace_callback\s*\( 115 | 116 | 117 | - ID: PHP_REFLECTION_INJ_001 118 | name: "Use PHP reflection mechanism to control program flow" 119 | severity: Medium 120 | confidence: Low 121 | references: ['https://www.owasp.org/index.php/Unsafe_use_of_Reflection'] 122 | patterns: 123 | - \bevent_buffer_new\s*\( 124 | - \bevent_set\s*\( 125 | - \biterator_apply\s*\( 126 | - \bforward_static_call\s*\( 127 | - \bforward_static_call_array\s*\( 128 | - \bcall_user_func\s*\( 129 | - \bcall_user_func_array\s*\( 130 | - \barray_diff_uassoc\s*\( 131 | - \barray_diff_ukey\s*\( 132 | - \barray_filter\s*\( 133 | - \barray_intersect_uassoc\s*\( 134 | - \barray_intersect_ukey\s*\( 135 | - \barray_map\s*\( 136 | - \barray_reduce\s*\( 137 | - \barray_udiff\s*\( 138 | - \barray_udiff_assoc\s*\( 139 | - \barray_udiff_uassoc\s*\( 140 | - \barray_uintersect\s*\( 141 | - \barray_uintersect_assoc\s*\( 142 | - \barray_uintersect_uassoc\s*\( 143 | - \barray_walk\s*\( 144 | - \barray_walk_recursive\s*\( 145 | - \bassert_options\s*\( 146 | - \bregister_shutdown_function\s*\( 147 | - \bregister_tick_function\s*\( 148 | - \brunkit_method_add\s*\( 149 | - \brunkit_method_copy\s*\( 150 | - \brunkit_method_redefine\s*\( 151 | - \brunkit_method_rename\s*\( 152 | - \brunkit_function_add\s*\( 153 | - \brunkit_function_copy\s*\( 154 | - \brunkit_function_redefine\s*\( 155 | - \brunkit_function_rename\s*\( 156 | - \bsession_set_save_handler\s*\( 157 | - \bset_error_handler\s*\( 158 | - \bset_exception_handler\s*\( 159 | - \bspl_autoload\s*\( 160 | - \bspl_autoload_register\s*\( 161 | - \bsqlite_create_aggregate\s*\( 162 | - \bsqlite_create_function\s*\( 163 | - \bstream_wrapper_register\s*\( 164 | - \buasort\s*\( 165 | - \buksort\s*\( 166 | - \busort\s*\( 167 | - \byaml_parse\s*\( 168 | - \byaml_parse_file\s*\( 169 | - \byaml_parse_url\s*\( 170 | - \beio_busy\s*\( 171 | - \beio_chmod\s*\( 172 | - \beio_chown\s*\( 173 | - \beio_close\s*\( 174 | - \beio_custom\s*\( 175 | - \beio_dup2\s*\( 176 | - \beio_fallocate\s*\( 177 | - \beio_fchmod\s*\( 178 | - \beio_fchown\s*\( 179 | - \beio_fdatasync\s*\( 180 | - \beio_fstat\s*\( 181 | - \beio_fstatvfs\s*\( 182 | - \bpreg_replace_callback\s*\( 183 | - \bdotnet_load\s*\( 184 | 185 | 186 | - ID: PHP_FILE_INCLUSION_001 187 | name: "Evaluate php code in functions like 'eval'" 188 | severity: Medium 189 | confidence: Low 190 | references: [] 191 | patterns: 192 | - \binclude\s+ 193 | - \binclude_once\b 194 | - \bparsekit_compile_file\s*\( 195 | - \bphp_check_syntax\s*\( 196 | - \brequire\s+ 197 | - \brequire_once\b 198 | - \brunkit_import\s*\( 199 | - \bset_include_path\s*\( 200 | evaluate: php_file_inclusion_001_evaluate 201 | 202 | 203 | - ID: PHP_FILE_OPERATION_001 204 | name: "File operation" 205 | severity: Medium 206 | confidence: Low 207 | references: [] 208 | references: [] 209 | patterns: 210 | - \bbzwrite\s*\( 211 | - \bchmod\s*\( 212 | - \bchgrp\s*\( 213 | - \bchown\s*\( 214 | - \bcopy\s*\s*\( 215 | - \bdio_write\s*\( 216 | - \beio_chmod\s*\( 217 | - \beio_chown\s*\( 218 | - \beio_mkdir\s*\( 219 | - \beio_mknod\s*\( 220 | - \beio_rmdir\s*\( 221 | - \beio_write\s*\( 222 | - \beio_unlink\s*\( 223 | - \berror_log\s*\( 224 | - \bevent_buffer_write\s*\( 225 | - \bfile_put_contents\s*\( 226 | - \bfputcsv\s*\( 227 | - \bfputs\s*\( 228 | - \bfprintf\s*\( 229 | - \bftruncate\s*\( 230 | - \bfwrite\s*\( 231 | - \bgzwrite\s*\( 232 | - \bgzputs\s*\( 233 | - \bloadXML\s*\( 234 | - \bmkdir\s*\( 235 | - \bmove_uploaded_file\s*\( 236 | - \bposix_mknod\s*\( 237 | - \brecode_file\s*\( 238 | - \brename\s*\( 239 | - \brmdir\s*\( 240 | - \bshmop_write\s*\( 241 | - \btouch\s*\( 242 | - \bunlink\s*\s*\( 243 | - \bvfprintf\s*\( 244 | - \bxdiff_file_bdiff\s*\( 245 | - \bxdiff_file_bpatch\s*\( 246 | - \bxdiff_file_diff_binary\s*\( 247 | - \bxdiff_file_diff\s*\( 248 | - \bxdiff_file_merge3\s*\( 249 | - \bxdiff_file_patch_binary\s*\( 250 | - \bxdiff_file_patch\s*\( 251 | - \bxdiff_file_rabdiff\s*\( 252 | - \byaml_emit_file\s*\( 253 | 254 | 255 | - ID: PHP_XPATH_INJ_001 256 | name: "Execute xpath command" 257 | severity: High 258 | confidence: Medium 259 | references: [] 260 | patterns: 261 | - \bxpath_eval\s*\( 262 | - \bxpath_eval_expression\s*\( 263 | - \bxptr_eval\s*\( 264 | 265 | 266 | - ID: PHP_LDAP_INJ_001 267 | name: "Evaluate php code in functions like 'eval'" 268 | severity: High 269 | confidence: Medium 270 | references: [] 271 | patterns: 272 | - \bldap_add\s*\( 273 | - \bldap_delete\s*\( 274 | - \bldap_list\s*\( 275 | - \bldap_read\s*\( 276 | - \bldap_search\s*\( 277 | 278 | 279 | 280 | - ID: PHP_PROTOCOL_INJ_001 281 | name: "Protocol inject" 282 | severity: Medium 283 | confidence: Low 284 | references: [] 285 | patterns: 286 | - \bcurl_setopt\s*\( 287 | - \bcurl_setopt_array\s*\( 288 | - \bcyrus_query\s*\( 289 | - \berror_log\s*\( 290 | - \bfsockopen\s*\( 291 | - \bftp_chmod\s*\( 292 | - \bftp_exec\s*\( 293 | - \bftp_delete\s*\( 294 | - \bftp_fget\s*\( 295 | - \bftp_get\s*\( 296 | - \bftp_nlist\s*\( 297 | - \bftp_nb_fget\s*\( 298 | - \bftp_nb_get\s*\( 299 | - \bftp_nb_put\s*\( 300 | - \bftp_put\s*\( 301 | - \bget_headers\s*\( 302 | - \bimap_open\s*\( 303 | - \bimap_mail\s*\( 304 | - \bmb_send_mail\s*\( 305 | - \bldap_connect\s*\( 306 | - \bmsession_connect\s*\( 307 | - \bpfsockopen\s*\( 308 | - \bsession_register\s*\( 309 | - \bsocket_bind\s*\( 310 | - \bsocket_connect\s*\( 311 | - \bsocket_send\s*\( 312 | - \bsocket_write\s*\( 313 | - \bstream_socket_client\s*\( 314 | - \bstream_socket_server\s*\( 315 | - \bprinter_open\s*\( 316 | 317 | 318 | 319 | - ID: PHP_OBJ_INJ_001 320 | name: "Serialize operation" 321 | severity: High 322 | confidence: Low 323 | references: [] 324 | patterns: 325 | - \bunserialize\s*\( 326 | - \byaml_parse\s*\( 327 | 328 | 329 | -------------------------------------------------------------------------------- /kiwi_data/features/python.feature: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwi, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # Issue defines for python 9 | #=============================================================================== 10 | 11 | version: 1.0 12 | 13 | scopes: 14 | - python 15 | 16 | 17 | features: 18 | - ID: PY_CMD_INJ_001 19 | name: "Use 'os/commands' module to execute command in shells" 20 | references: [] 21 | severity: High 22 | confidence: High 23 | patterns: 24 | - os.system 25 | - os.popen 26 | - os.popen2 27 | - os.popen3 28 | - os.popen4 29 | - popen2.popen2 30 | - popen2.popen3 31 | - popen2.popen4 32 | - popen2.Popen3 33 | - popen2.Popen4 34 | - commands.getoutput 35 | - commands.getstatusoutput 36 | 37 | 38 | - ID: PY_CMD_INJ_002 39 | name: "Use 'subprocess' module with shell=True to execute command in shells" 40 | severity: High 41 | confidence: Medium 42 | references: [] 43 | patterns: 44 | - subprocess.call 45 | - subprocess.Popen 46 | - subprocess.check_call 47 | - subprocess.check_output 48 | - utils.execute 49 | - utils.execute_with_timeout 50 | evaluate: py_cmd_inject_0002_evaluate 51 | 52 | 53 | - ID: PY_CMD_EXE_001 54 | name: "File system operation" 55 | severity: Medium 56 | confidence: High 57 | 58 | references: [] 59 | patterns: 60 | - os.remove 61 | - os.unlink 62 | - os.rmdir 63 | - os.removedirs 64 | - shutil.rmtree 65 | 66 | 67 | - ID: PY_OBJ_INJ_001 68 | name: "Pickle operation" 69 | severity: High 70 | confidence: Low 71 | references: [] 72 | patterns: 73 | - pickle/cPickle 74 | - pickle.load 75 | - pickle.dump 76 | - yaml.load 77 | - yaml.dump 78 | 79 | 80 | - ID: PY_SQL_INJ_001 81 | name: "SQL injection" 82 | severity: High 83 | confidence: Low 84 | references: [] 85 | patterns: 86 | - \["']select.+from 87 | - \["']insert.+into 88 | - \["']update.+set 89 | - \["']drop 90 | - \["']delete.+from 91 | 92 | 93 | - ID: PY_EXEC_FUNC_0001 94 | name: "Evaluate a string" 95 | severity: High 96 | confidence: Medium 97 | references: [] 98 | patterns: 99 | - \beval\b 100 | - \beval_r\b 101 | - \bexec\b 102 | - \bexecfile\b 103 | 104 | 105 | - ID: PY_RUNING_IMPORT_0001 106 | name: "Unsafe import" 107 | severity: Low 108 | confidence: Low 109 | references: [] 110 | patterns: 111 | - __import__ 112 | - importlib.import_module 113 | - imp.load_module 114 | - imp.load_source 115 | - imp.load_compiled 116 | - imp.load_dynamic 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /kiwi_data/features/raw.feature: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwi, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # Sensitive information leaking 9 | #=============================================================================== 10 | 11 | version: 1.0 12 | 13 | engine: GrepEngine 14 | 15 | scopes: 16 | - raw 17 | 18 | features: 19 | - ID: RAW_PASSWD_INFO_001 20 | name: "Password information leaking" 21 | severity: High 22 | confidence: High 23 | references: [] 24 | patterns: 25 | - password 26 | -------------------------------------------------------------------------------- /kiwi_data/filemap: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwi, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # file mapping configuration 9 | #=============================================================================== 10 | 11 | 12 | extensions: 13 | - pattern: "\\.py$" 14 | scope: python 15 | 16 | - pattern: "\\.php\\d+$" 17 | scope: php 18 | 19 | - pattern: "\\.java$" 20 | scope: java 21 | 22 | - pattern: "\\.sh$" 23 | scope: linux-shell 24 | 25 | metainfos: 26 | - pattern: "#!/usr/bin\\w+python" 27 | scope: python 28 | 29 | - pattern: " 2 | 3 | 4 | 5 | author 6 | github.com/alpha1e0 7 | colorSpaceName 8 | sRGB 9 | gutterSettings 10 | 11 | background 12 | #073642 13 | divider 14 | #586e75 15 | foreground 16 | #839496 17 | selectionBackground 18 | #586e75 19 | selectionForeground 20 | #a6e22e 21 | 22 | name 23 | Monokai Extended 24 | semanticClass 25 | theme.dark.monokai_extended 26 | settings 27 | 28 | 29 | settings 30 | 31 | activeGuide 32 | #9d550fb0 33 | background 34 | #222222 35 | bracketContentsForeground 36 | #f8f8f2a5 37 | bracketContentsOptions 38 | underline 39 | bracketsForeground 40 | #f8f8f2a5 41 | bracketsOptions 42 | underline 43 | caret 44 | #f8f8f0 45 | findHighlight 46 | #ffe792 47 | findHighlightForeground 48 | #000000 49 | foreground 50 | #f8f8f2 51 | invisibles 52 | #3b3a32 53 | lineHighlight 54 | #333333 55 | selection 56 | #444444 57 | selectionBorder 58 | #1c1c1c 59 | tagsOptions 60 | stippled_underline 61 | 62 | 63 | 64 | name 65 | Comment 66 | scope 67 | comment 68 | settings 69 | 70 | foreground 71 | #75715e 72 | 73 | 74 | 75 | name 76 | String 77 | scope 78 | string 79 | settings 80 | 81 | foreground 82 | #e6db74 83 | 84 | 85 | 86 | name 87 | Number 88 | scope 89 | constant.numeric 90 | settings 91 | 92 | foreground 93 | #be84ff 94 | 95 | 96 | 97 | name 98 | Constant: Built-in 99 | scope 100 | constant.language, meta.preprocessor 101 | settings 102 | 103 | foreground 104 | #be84ff 105 | 106 | 107 | 108 | name 109 | Constant: User-defined 110 | scope 111 | constant.character, constant.other 112 | settings 113 | 114 | foreground 115 | #be84ff 116 | 117 | 118 | 119 | name 120 | Variable 121 | scope 122 | variable.language, variable.other 123 | settings 124 | 125 | foreground 126 | #ffffff 127 | 128 | 129 | 130 | name 131 | Keyword 132 | scope 133 | keyword 134 | settings 135 | 136 | foreground 137 | #f92672 138 | 139 | 140 | 141 | name 142 | Storage 143 | scope 144 | storage 145 | settings 146 | 147 | fontStyle 148 | 149 | foreground 150 | #f92672 151 | 152 | 153 | 154 | name 155 | Storage type 156 | scope 157 | storage.type 158 | settings 159 | 160 | fontStyle 161 | italic 162 | foreground 163 | #66d9ef 164 | 165 | 166 | 167 | name 168 | Class name 169 | scope 170 | entity.name.class 171 | settings 172 | 173 | fontStyle 174 | underline 175 | foreground 176 | #66D9EF 177 | 178 | 179 | 180 | name 181 | Inherited class 182 | scope 183 | entity.other.inherited-class 184 | settings 185 | 186 | fontStyle 187 | italic underline 188 | foreground 189 | #a6e22e 190 | 191 | 192 | 193 | name 194 | Function name 195 | scope 196 | entity.name.function 197 | settings 198 | 199 | fontStyle 200 | 201 | foreground 202 | #a6e22e 203 | 204 | 205 | 206 | name 207 | Function argument 208 | scope 209 | variable.parameter 210 | settings 211 | 212 | fontStyle 213 | italic 214 | foreground 215 | #fd971f 216 | 217 | 218 | 219 | name 220 | Tag name 221 | scope 222 | entity.name.tag 223 | settings 224 | 225 | fontStyle 226 | 227 | foreground 228 | #f92672 229 | 230 | 231 | 232 | name 233 | Tag attribute 234 | scope 235 | entity.other.attribute-name 236 | settings 237 | 238 | fontStyle 239 | 240 | foreground 241 | #a6e22e 242 | 243 | 244 | 245 | name 246 | Library function 247 | scope 248 | support.function 249 | settings 250 | 251 | fontStyle 252 | 253 | foreground 254 | #66d9ef 255 | 256 | 257 | 258 | name 259 | Library constant 260 | scope 261 | support.constant 262 | settings 263 | 264 | fontStyle 265 | 266 | foreground 267 | #66d9ef 268 | 269 | 270 | 271 | name 272 | Library class/type 273 | scope 274 | support.type, support.class 275 | settings 276 | 277 | fontStyle 278 | italic 279 | foreground 280 | #A6E22E 281 | 282 | 283 | 284 | name 285 | Library variable 286 | scope 287 | support.other.variable 288 | settings 289 | 290 | fontStyle 291 | 292 | 293 | 294 | 295 | name 296 | String constant 297 | scope 298 | string constant 299 | settings 300 | 301 | foreground 302 | #66d9ef 303 | 304 | 305 | 306 | name 307 | String.regexp 308 | scope 309 | string.regexp 310 | settings 311 | 312 | foreground 313 | #f6aa11 314 | 315 | 316 | 317 | name 318 | String variable 319 | scope 320 | string variable 321 | settings 322 | 323 | foreground 324 | #ffffff 325 | 326 | 327 | 328 | name 329 | Variable: punctuation 330 | scope 331 | punctuation.definition.variable 332 | settings 333 | 334 | foreground 335 | #ffffff 336 | 337 | 338 | 339 | name 340 | Entity 341 | scope 342 | entity 343 | settings 344 | 345 | fontStyle 346 | 347 | foreground 348 | #a6e22e 349 | 350 | 351 | 352 | name 353 | HTML: Doctype/XML Processing 354 | scope 355 | meta.tag.sgml.doctype.xml, declaration.sgml.html declaration.doctype, declaration.sgml.html declaration.doctype entity, declaration.sgml.html declaration.doctype string, declaration.xml-processing, declaration.xml-processing entity, declaration.xml-processing string, doctype 356 | settings 357 | 358 | foreground 359 | #c8cecc 360 | 361 | 362 | 363 | name 364 | HTML: Comment Block 365 | scope 366 | comment.block.html 367 | settings 368 | 369 | fontStyle 370 | 371 | foreground 372 | #7c7865 373 | 374 | 375 | 376 | name 377 | HTML: Script 378 | scope 379 | entity.name.tag.script.html 380 | settings 381 | 382 | fontStyle 383 | italic 384 | 385 | 386 | 387 | name 388 | HTML: Attribute punctuation 389 | scope 390 | text.html.basic meta.tag.other.html, text.html.basic meta.tag.any.html, text.html.basic meta.tag.block.any, text.html.basic meta.tag.inline.any, text.html.basic meta.tag.structure.any.html, text.html.basic source.js.embedded.html, punctuation.separator.key-value.html 391 | settings 392 | 393 | fontStyle 394 | 395 | foreground 396 | #a6e22e 397 | 398 | 399 | 400 | name 401 | HTML: Attributes 402 | scope 403 | text.html.basic entity.other.attribute-name.html 404 | settings 405 | 406 | foreground 407 | #a6e22e 408 | 409 | 410 | 411 | name 412 | HTML: Quotation Marks 413 | scope 414 | text.html.basic meta.tag.structure.any.html punctuation.definition.string.begin.html, punctuation.definition.string.begin.html, punctuation.definition.string.end.html 415 | settings 416 | 417 | fontStyle 418 | 419 | foreground 420 | #ffffff 421 | 422 | 423 | 424 | name 425 | HTML: Tags punctuation 426 | scope 427 | punctuation.definition.tag.end, punctuation.definition.tag.begin, punctuation.definition.tag 428 | settings 429 | 430 | foreground 431 | #ffffff 432 | 433 | 434 | 435 | name 436 | Handlebars: Variable 437 | scope 438 | variable.parameter.handlebars 439 | settings 440 | 441 | foreground 442 | #f6aa11 443 | 444 | 445 | 446 | name 447 | Handlebars: Constant 448 | scope 449 | support.constant.handlebars, meta.function.block.start.handlebars 450 | settings 451 | 452 | foreground 453 | #66d9ef 454 | 455 | 456 | 457 | name 458 | CSS: @at-rule 459 | scope 460 | meta.preprocessor.at-rule keyword.control.at-rule 461 | settings 462 | 463 | foreground 464 | #f6aa11 465 | 466 | 467 | 468 | name 469 | CSS: #Id 470 | scope 471 | meta.selector.css entity.other.attribute-name.id 472 | settings 473 | 474 | foreground 475 | #f6aa11 476 | 477 | 478 | 479 | name 480 | CSS: .class 481 | scope 482 | meta.selector.css entity.other.attribute-name.class 483 | settings 484 | 485 | foreground 486 | #a6e22e 487 | 488 | 489 | 490 | name 491 | CSS: Property Name 492 | scope 493 | support.type.property-name.css 494 | settings 495 | 496 | foreground 497 | #66d9ef 498 | 499 | 500 | 501 | name 502 | CSS: Constructor Argument 503 | scope 504 | meta.constructor.argument.css 505 | settings 506 | 507 | foreground 508 | #f6aa11 509 | 510 | 511 | 512 | name 513 | CSS: {} 514 | scope 515 | punctuation.section.property-list.css 516 | settings 517 | 518 | foreground 519 | #ffffff 520 | 521 | 522 | 523 | name 524 | CSS: Tag Punctuation 525 | scope 526 | punctuation.definition.tag.css 527 | settings 528 | 529 | foreground 530 | #f92672 531 | 532 | 533 | 534 | name 535 | CSS: : , 536 | scope 537 | punctuation.separator.key-value.css, punctuation.terminator.rule.css 538 | settings 539 | 540 | fontStyle 541 | 542 | foreground 543 | #ffffff 544 | 545 | 546 | 547 | name 548 | CSS :pseudo 549 | scope 550 | entity.other.attribute-name.pseudo-element.css, entity.other.attribute-name.pseudo-class.css, entity.other.attribute-name.pseudo-selector.css 551 | settings 552 | 553 | fontStyle 554 | 555 | foreground 556 | #a6e22e 557 | 558 | 559 | 560 | name 561 | LESS variables 562 | scope 563 | variable.other.less 564 | settings 565 | 566 | foreground 567 | #ffffff 568 | 569 | 570 | 571 | name 572 | LESS mixins 573 | scope 574 | entity.other.less.mixin 575 | settings 576 | 577 | fontStyle 578 | italic 579 | foreground 580 | #e0fdce 581 | 582 | 583 | 584 | name 585 | LESS: Extend 586 | scope 587 | entity.other.attribute-name.pseudo-element.less 588 | settings 589 | 590 | fontStyle 591 | 592 | foreground 593 | #ff9117 594 | 595 | 596 | 597 | name 598 | JS: Instance constructor 599 | scope 600 | meta.instance.constructor meta.function-call.constructor.js 601 | settings 602 | 603 | fontStyle 604 | 605 | foreground 606 | #a6e22e 607 | 608 | 609 | 610 | name 611 | JS: es6 template delimiters ${} 612 | scope 613 | meta.template.expression.js punctuation.definition.template-expression.begin.js, meta.template.expression.js punctuation.definition.template-expression.end.js, meta.template.expression.js punctuation.accessor 614 | settings 615 | 616 | fontStyle 617 | 618 | foreground 619 | #AFF132 620 | 621 | 622 | 623 | name 624 | JS: Function Name 625 | scope 626 | meta.function.js, entity.name.function.js, support.function.dom.js 627 | settings 628 | 629 | fontStyle 630 | 631 | foreground 632 | #a6e22e 633 | 634 | 635 | 636 | name 637 | JS: Param commas 638 | scope 639 | source.js meta.function.js punctuation.separator.parameter.function.js 640 | settings 641 | 642 | fontStyle 643 | 644 | foreground 645 | #ffffff 646 | 647 | 648 | 649 | name 650 | JS: Object dot notation 651 | scope 652 | meta.property.object.js, keyword.operator.accessor.js 653 | settings 654 | 655 | fontStyle 656 | 657 | foreground 658 | #ffffff 659 | 660 | 661 | 662 | name 663 | JS: colons 664 | scope 665 | source.js meta.group.braces.curly constant.other.object.key.js punctuation.separator.key-value.js 666 | settings 667 | 668 | fontStyle 669 | 670 | foreground 671 | #ffffff 672 | 673 | 674 | 675 | name 676 | JS: unquoted string 677 | scope 678 | source.js meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js 679 | settings 680 | 681 | fontStyle 682 | 683 | foreground 684 | #ffffff 685 | 686 | 687 | 688 | name 689 | JS: receiver 690 | scope 691 | support.type.object.module.js, source.js meta.function.declaration.js support.class.js 692 | settings 693 | 694 | fontStyle 695 | 696 | foreground 697 | #66d9ef 698 | 699 | 700 | 701 | name 702 | JS: receiver property 703 | scope 704 | support.type.object.module.js support.type.object.module.js 705 | settings 706 | 707 | fontStyle 708 | 709 | foreground 710 | #a6e22e 711 | 712 | 713 | 714 | name 715 | JS: Storage Type 716 | scope 717 | storage.type.js 718 | settings 719 | 720 | fontStyle 721 | italic 722 | foreground 723 | #66d9ef 724 | 725 | 726 | 727 | name 728 | JS: Source 729 | scope 730 | text.html.basic source.js.embedded.html 731 | settings 732 | 733 | fontStyle 734 | 735 | foreground 736 | #ffffff 737 | 738 | 739 | 740 | name 741 | JS: Function 742 | scope 743 | storage.type.function.js 744 | settings 745 | 746 | fontStyle 747 | italic 748 | foreground 749 | #66d9ef 750 | 751 | 752 | 753 | name 754 | JS: Numeric Constant 755 | scope 756 | constant.numeric.js 757 | settings 758 | 759 | foreground 760 | #ae81ff 761 | 762 | 763 | 764 | name 765 | JS: Literal language variable 766 | scope 767 | variable.language.arguments.js, variable.language.super.js, variable.language.this.js, variable.language.self.js, variable.language.proto.js, variable.language.constructor.js, variable.language.prototype.js 768 | settings 769 | 770 | fontStyle 771 | italic 772 | foreground 773 | #66d9ef 774 | 775 | 776 | 777 | name 778 | JS: [] 779 | scope 780 | meta.brace.square.js 781 | settings 782 | 783 | foreground 784 | #ffffff 785 | 786 | 787 | 788 | name 789 | JS: () 790 | scope 791 | meta.brace.round, punctuation.definition.parameters.begin.js, punctuation.definition.parameters.end.js, punctuation.definition.group 792 | settings 793 | 794 | foreground 795 | #ffffff 796 | 797 | 798 | 799 | name 800 | JS: object literal {} 801 | scope 802 | meta.brace.curly.js, meta.object-literal.js 803 | settings 804 | 805 | foreground 806 | #ffffff 807 | 808 | 809 | 810 | name 811 | JSON String 812 | scope 813 | meta.structure.dictionary.json string.quoted.double.json 814 | settings 815 | 816 | foreground 817 | #cfcfc2 818 | 819 | 820 | 821 | name 822 | CoffeeScript String Interpolated 823 | scope 824 | punctuation.section.embedded.coffee 825 | settings 826 | 827 | foreground 828 | #e69f66 829 | 830 | 831 | 832 | name 833 | PHP: [] 834 | scope 835 | keyword.operator.index-start.php, keyword.operator.index-end.php 836 | settings 837 | 838 | foreground 839 | #ffffff 840 | 841 | 842 | 843 | name 844 | PHP: Array 845 | scope 846 | meta.array.php 847 | settings 848 | 849 | foreground 850 | #ffffff 851 | 852 | 853 | 854 | name 855 | PHP: Array() 856 | scope 857 | meta.array.php support.function.construct.php, meta.array.empty.php support.function.construct.php 858 | settings 859 | 860 | fontStyle 861 | 862 | foreground 863 | #e42e70 864 | 865 | 866 | 867 | name 868 | PHP: Array Construct 869 | scope 870 | support.function.construct.php 871 | settings 872 | 873 | foreground 874 | #e42e70 875 | 876 | 877 | 878 | name 879 | PHP: Storage Type Function 880 | scope 881 | storage.type.function.php 882 | settings 883 | 884 | foreground 885 | #f92672dd 886 | 887 | 888 | 889 | name 890 | PHP: Numeric Constant 891 | scope 892 | constant.numeric.php 893 | settings 894 | 895 | foreground 896 | #be84ff 897 | 898 | 899 | 900 | name 901 | PHP: New 902 | scope 903 | keyword.other.new.php 904 | settings 905 | 906 | foreground 907 | #f6aa11 908 | 909 | 910 | 911 | name 912 | PHP: :: 913 | scope 914 | support.class.php 915 | settings 916 | 917 | fontStyle 918 | 919 | foreground 920 | #ffffff 921 | 922 | 923 | 924 | name 925 | PHP: Other Property 926 | scope 927 | variable.other.property.php 928 | settings 929 | 930 | foreground 931 | #f6aa11 932 | 933 | 934 | 935 | name 936 | PHP: Class 937 | scope 938 | storage.modifier.extends.php, storage.type.class.php, keyword.operator.class.php 939 | settings 940 | 941 | foreground 942 | #a6e22e 943 | 944 | 945 | 946 | name 947 | PHP: Inherited Class 948 | scope 949 | meta.other.inherited-class.php 950 | settings 951 | 952 | fontStyle 953 | 954 | foreground 955 | #a6e22e 956 | 957 | 958 | 959 | name 960 | PHP: Storage Type 961 | scope 962 | storage.type.php 963 | settings 964 | 965 | foreground 966 | #66d9ef 967 | 968 | 969 | 970 | name 971 | PHP: Function 972 | scope 973 | entity.name.function.php 974 | settings 975 | 976 | foreground 977 | #66d9ef 978 | 979 | 980 | 981 | name 982 | PHP: Function Construct 983 | scope 984 | support.function.construct.php 985 | settings 986 | 987 | foreground 988 | #a6e22e 989 | 990 | 991 | 992 | name 993 | PHP: Function Call 994 | scope 995 | entity.name.type.class.php, meta.function-call.php, meta.function-call.static.php, meta.function-call.object.php 996 | settings 997 | 998 | foreground 999 | #ffffff 1000 | 1001 | 1002 | 1003 | name 1004 | PHP: Comment 1005 | scope 1006 | keyword.other.phpdoc 1007 | settings 1008 | 1009 | fontStyle 1010 | 1011 | foreground 1012 | #7c7865 1013 | 1014 | 1015 | 1016 | name 1017 | PHP: Source Emebedded 1018 | scope 1019 | source.php.embedded.block.html 1020 | settings 1021 | 1022 | foreground 1023 | #ffffff 1024 | 1025 | 1026 | 1027 | name 1028 | Invalid 1029 | scope 1030 | invalid 1031 | settings 1032 | 1033 | background 1034 | #f92672 1035 | fontStyle 1036 | 1037 | foreground 1038 | #f8f8f0 1039 | 1040 | 1041 | 1042 | name 1043 | Invalid deprecated 1044 | scope 1045 | invalid.deprecated 1046 | settings 1047 | 1048 | background 1049 | #ae81ff 1050 | foreground 1051 | #f8f8f0 1052 | 1053 | 1054 | 1055 | name 1056 | diff.header 1057 | scope 1058 | meta.diff, meta.diff.header 1059 | settings 1060 | 1061 | foreground 1062 | #75715e 1063 | 1064 | 1065 | 1066 | name 1067 | diff.deleted 1068 | scope 1069 | markup.deleted 1070 | settings 1071 | 1072 | foreground 1073 | #f92672 1074 | 1075 | 1076 | 1077 | name 1078 | diff.inserted 1079 | scope 1080 | markup.inserted 1081 | settings 1082 | 1083 | foreground 1084 | #a6e22e 1085 | 1086 | 1087 | 1088 | name 1089 | diff.changed 1090 | scope 1091 | markup.changed 1092 | settings 1093 | 1094 | foreground 1095 | #e6db74 1096 | 1097 | 1098 | 1099 | name 1100 | diff.range 1101 | scope 1102 | meta.diff, meta.diff.range 1103 | settings 1104 | 1105 | foreground 1106 | #3bc0f0 1107 | 1108 | 1109 | 1110 | name 1111 | Python: storage 1112 | scope 1113 | storage.type.class.python, storage.type.function.python, storage.modifier.global.python 1114 | settings 1115 | 1116 | fontStyle 1117 | 1118 | foreground 1119 | #a6e22e 1120 | 1121 | 1122 | 1123 | name 1124 | Python: import 1125 | scope 1126 | keyword.control.import.python, keyword.control.import.from.python 1127 | settings 1128 | 1129 | foreground 1130 | #f92672dd 1131 | 1132 | 1133 | 1134 | name 1135 | Python: Support.exception 1136 | scope 1137 | support.type.exception.python 1138 | settings 1139 | 1140 | foreground 1141 | #66d9ef 1142 | 1143 | 1144 | 1145 | name 1146 | Perl: variables 1147 | scope 1148 | punctuation.definition.variable.perl, variable.other.readwrite.global.perl, variable.other.predefined.perl, keyword.operator.comparison.perl 1149 | settings 1150 | 1151 | foreground 1152 | #e42e70 1153 | 1154 | 1155 | 1156 | name 1157 | Perl: functions 1158 | scope 1159 | support.function.perl 1160 | settings 1161 | 1162 | foreground 1163 | #66d9ef 1164 | 1165 | 1166 | 1167 | name 1168 | Perl: comments 1169 | scope 1170 | comment.line.number-sign.perl 1171 | settings 1172 | 1173 | fontStyle 1174 | italic 1175 | foreground 1176 | #75715e 1177 | 1178 | 1179 | 1180 | name 1181 | Perl: quotes 1182 | scope 1183 | punctuation.definition.string.begin.perl, punctuation.definition.string.end.perl 1184 | settings 1185 | 1186 | foreground 1187 | #ffffff 1188 | 1189 | 1190 | 1191 | name 1192 | Perl: char 1193 | scope 1194 | constant.character.escape.perl 1195 | settings 1196 | 1197 | foreground 1198 | #dc322f 1199 | 1200 | 1201 | 1202 | name 1203 | Ruby: Constant 1204 | scope 1205 | constant.language.ruby, constant.numeric.ruby 1206 | settings 1207 | 1208 | foreground 1209 | #ae81ff 1210 | 1211 | 1212 | 1213 | name 1214 | Ruby: Variable definition 1215 | scope 1216 | punctuation.definition.variable.ruby 1217 | settings 1218 | 1219 | fontStyle 1220 | 1221 | foreground 1222 | #f6aa11 1223 | 1224 | 1225 | 1226 | name 1227 | Ruby: Function Name 1228 | scope 1229 | meta.function.method.with-arguments.ruby 1230 | settings 1231 | 1232 | foreground 1233 | #a6e22e 1234 | 1235 | 1236 | 1237 | name 1238 | Ruby: Variable 1239 | scope 1240 | variable.language.ruby 1241 | settings 1242 | 1243 | foreground 1244 | #ffffff 1245 | 1246 | 1247 | 1248 | name 1249 | Ruby: Function 1250 | scope 1251 | entity.name.function.ruby 1252 | settings 1253 | 1254 | foreground 1255 | #f6aa11 1256 | 1257 | 1258 | 1259 | name 1260 | Ruby: Keyword Control 1261 | scope 1262 | keyword.control.ruby, keyword.control.def.ruby 1263 | settings 1264 | 1265 | fontStyle 1266 | bold 1267 | foreground 1268 | #a6e22e 1269 | 1270 | 1271 | 1272 | name 1273 | Ruby: Class 1274 | scope 1275 | keyword.control.class.ruby, meta.class.ruby 1276 | settings 1277 | 1278 | foreground 1279 | #a6e22e 1280 | 1281 | 1282 | 1283 | name 1284 | Ruby: Class Name 1285 | scope 1286 | entity.name.type.class.ruby 1287 | settings 1288 | 1289 | fontStyle 1290 | 1291 | foreground 1292 | #66d9ef 1293 | 1294 | 1295 | 1296 | name 1297 | Ruby: Keyword 1298 | scope 1299 | keyword.control.ruby 1300 | settings 1301 | 1302 | fontStyle 1303 | 1304 | foreground 1305 | #a6e22e 1306 | 1307 | 1308 | 1309 | name 1310 | Ruby: Support Class 1311 | scope 1312 | support.class.ruby 1313 | settings 1314 | 1315 | fontStyle 1316 | 1317 | foreground 1318 | #66d9ef 1319 | 1320 | 1321 | 1322 | name 1323 | Ruby: Special Method 1324 | scope 1325 | keyword.other.special-method.ruby 1326 | settings 1327 | 1328 | foreground 1329 | #a6e22e 1330 | 1331 | 1332 | 1333 | name 1334 | Ruby: Constant Other 1335 | scope 1336 | variable.other.constant.ruby 1337 | settings 1338 | 1339 | fontStyle 1340 | 1341 | foreground 1342 | #66d9ef 1343 | 1344 | 1345 | 1346 | name 1347 | Ruby: :symbol 1348 | scope 1349 | constant.other.symbol.ruby 1350 | settings 1351 | 1352 | fontStyle 1353 | 1354 | foreground 1355 | #f6f080 1356 | 1357 | 1358 | 1359 | name 1360 | Ruby: Punctuation Section 1361 | scope 1362 | punctuation.section.embedded.ruby, punctuation.definition.string.begin.ruby, punctuation.definition.string.end.ruby 1363 | settings 1364 | 1365 | foreground 1366 | #f92672 1367 | 1368 | 1369 | 1370 | name 1371 | Ruby: Special Method 1372 | scope 1373 | keyword.other.special-method.ruby 1374 | settings 1375 | 1376 | foreground 1377 | #e42e70 1378 | 1379 | 1380 | 1381 | name 1382 | Markdown: plain 1383 | scope 1384 | text.html.markdown 1385 | settings 1386 | 1387 | foreground 1388 | #ffffff 1389 | 1390 | 1391 | 1392 | name 1393 | Markup: raw inline 1394 | scope 1395 | text.html.markdown markup.raw.inline 1396 | settings 1397 | 1398 | foreground 1399 | #ec3533 1400 | 1401 | 1402 | 1403 | name 1404 | Markdown: linebreak 1405 | scope 1406 | text.html.markdown meta.dummy.line-break 1407 | settings 1408 | 1409 | foreground 1410 | #e0eddd 1411 | 1412 | 1413 | 1414 | name 1415 | Markdown: heading 1416 | scope 1417 | markdown.heading, markup.heading | markup.heading entity.name, markup.heading.markdown punctuation.definition.heading.markdown 1418 | settings 1419 | 1420 | fontStyle 1421 | 1422 | foreground 1423 | #fd971f 1424 | 1425 | 1426 | 1427 | name 1428 | Markup: italic 1429 | scope 1430 | markup.italic 1431 | settings 1432 | 1433 | fontStyle 1434 | italic 1435 | foreground 1436 | #e42e70 1437 | 1438 | 1439 | 1440 | name 1441 | Markup: bold 1442 | scope 1443 | markup.bold 1444 | settings 1445 | 1446 | fontStyle 1447 | bold 1448 | foreground 1449 | #f92672 1450 | 1451 | 1452 | 1453 | name 1454 | Markup: underline 1455 | scope 1456 | markup.underline 1457 | settings 1458 | 1459 | fontStyle 1460 | underline 1461 | foreground 1462 | #a6e22e 1463 | 1464 | 1465 | 1466 | name 1467 | Markup: strike 1468 | scope 1469 | markup.strike 1470 | settings 1471 | 1472 | fontStyle 1473 | 1474 | foreground 1475 | #cc4273 1476 | 1477 | 1478 | 1479 | name 1480 | Markdown: Blockquote 1481 | scope 1482 | markup.quote, punctuation.definition.blockquote.markdown 1483 | settings 1484 | 1485 | fontStyle 1486 | italic 1487 | foreground 1488 | #66d9ef 1489 | 1490 | 1491 | 1492 | name 1493 | Markup: Quote 1494 | scope 1495 | markup.quote 1496 | settings 1497 | 1498 | fontStyle 1499 | italic 1500 | foreground 1501 | #66d9ef 1502 | 1503 | 1504 | 1505 | name 1506 | Markdown: Link 1507 | scope 1508 | string.other.link.title.markdown 1509 | settings 1510 | 1511 | fontStyle 1512 | underline 1513 | foreground 1514 | #66d9ef 1515 | 1516 | 1517 | 1518 | name 1519 | Markup: Raw block 1520 | scope 1521 | markup.raw.block 1522 | settings 1523 | 1524 | foreground 1525 | #ae81ff 1526 | 1527 | 1528 | 1529 | name 1530 | Markdown: List Items Punctuation 1531 | scope 1532 | punctuation.definition.list_item.markdown 1533 | settings 1534 | 1535 | foreground 1536 | #777777 1537 | 1538 | 1539 | 1540 | name 1541 | Markdown: Raw Block fenced 1542 | scope 1543 | markup.raw.block.fenced.markdown 1544 | settings 1545 | 1546 | background 1547 | #222 1548 | foreground 1549 | #ffffff 1550 | 1551 | 1552 | 1553 | name 1554 | Markdown: Fenced Bode Block 1555 | scope 1556 | punctuation.definition.fenced.markdown, variable.language.fenced.markdown 1557 | settings 1558 | 1559 | background 1560 | #222222 1561 | foreground 1562 | #636050 1563 | 1564 | 1565 | 1566 | name 1567 | Markdown: Fenced Language 1568 | scope 1569 | variable.language.fenced.markdown 1570 | settings 1571 | 1572 | fontStyle 1573 | 1574 | foreground 1575 | #7c7865 1576 | 1577 | 1578 | 1579 | name 1580 | Markdown: Separator 1581 | scope 1582 | meta.separator 1583 | settings 1584 | 1585 | background 1586 | #ffffff0f 1587 | fontStyle 1588 | bold 1589 | foreground 1590 | #ffffff33 1591 | 1592 | 1593 | 1594 | name 1595 | Markup: table 1596 | scope 1597 | markup.table 1598 | settings 1599 | 1600 | background 1601 | #ff3a281a 1602 | foreground 1603 | #b42a1d 1604 | 1605 | 1606 | 1607 | name 1608 | LaTeX: Math Variables 1609 | scope 1610 | variable.other.math.tex 1611 | settings 1612 | 1613 | foreground 1614 | #e6db74 1615 | 1616 | 1617 | 1618 | name 1619 | Other: Removal 1620 | scope 1621 | other.package.exclude, other.remove 1622 | settings 1623 | 1624 | fontStyle 1625 | 1626 | foreground 1627 | #d3201f 1628 | 1629 | 1630 | 1631 | name 1632 | Shell: builtin 1633 | scope 1634 | support.function.builtin.shell 1635 | settings 1636 | 1637 | foreground 1638 | #a6e22e 1639 | 1640 | 1641 | 1642 | name 1643 | Shell: variable 1644 | scope 1645 | variable.other.normal.shell 1646 | settings 1647 | 1648 | foreground 1649 | #66d9ef 1650 | 1651 | 1652 | 1653 | name 1654 | Shell: DOTFILES 1655 | scope 1656 | source.shell 1657 | settings 1658 | 1659 | fontStyle 1660 | 1661 | foreground 1662 | #ffffff 1663 | 1664 | 1665 | 1666 | name 1667 | Shell: meta scope in loop 1668 | scope 1669 | meta.scope.for-in-loop.shell, variable.other.loop.shell 1670 | settings 1671 | 1672 | fontStyle 1673 | 1674 | foreground 1675 | #fd971f 1676 | 1677 | 1678 | 1679 | name 1680 | Shell: Function name 1681 | scope 1682 | entity.name.function.shell 1683 | settings 1684 | 1685 | fontStyle 1686 | 1687 | foreground 1688 | #a6e22e 1689 | 1690 | 1691 | 1692 | name 1693 | Shell: Quotation Marks 1694 | scope 1695 | punctuation.definition.string.end.shell, punctuation.definition.string.begin.shell 1696 | settings 1697 | 1698 | fontStyle 1699 | 1700 | foreground 1701 | #ffffff 1702 | 1703 | 1704 | 1705 | name 1706 | Shell: Meta Block 1707 | scope 1708 | meta.scope.case-block.shell, meta.scope.case-body.shell 1709 | settings 1710 | 1711 | fontStyle 1712 | 1713 | foreground 1714 | #fd971f 1715 | 1716 | 1717 | 1718 | name 1719 | Shell: [] 1720 | scope 1721 | punctuation.definition.logical-expression.shell 1722 | settings 1723 | 1724 | fontStyle 1725 | 1726 | foreground 1727 | #ffffff 1728 | 1729 | 1730 | 1731 | name 1732 | Shell: Comment 1733 | scope 1734 | comment.line.number-sign.shell 1735 | settings 1736 | 1737 | fontStyle 1738 | italic 1739 | foreground 1740 | #7c7865 1741 | 1742 | 1743 | 1744 | name 1745 | Makefile: Comment 1746 | scope 1747 | comment.line.number-sign.makefile 1748 | settings 1749 | 1750 | fontStyle 1751 | 1752 | foreground 1753 | #7c7865 1754 | 1755 | 1756 | 1757 | name 1758 | Makefile: Comment punctuation 1759 | scope 1760 | punctuation.definition.comment.makefile 1761 | settings 1762 | 1763 | fontStyle 1764 | 1765 | foreground 1766 | #7c7865 1767 | 1768 | 1769 | 1770 | name 1771 | Makefile: Variables 1772 | scope 1773 | variable.other.makefile 1774 | settings 1775 | 1776 | fontStyle 1777 | 1778 | foreground 1779 | #f92672 1780 | 1781 | 1782 | 1783 | name 1784 | Makefile: Function name 1785 | scope 1786 | entity.name.function.makefile 1787 | settings 1788 | 1789 | fontStyle 1790 | 1791 | foreground 1792 | #a6e22e 1793 | 1794 | 1795 | 1796 | name 1797 | Makefile: Function 1798 | scope 1799 | meta.function.makefile 1800 | settings 1801 | 1802 | fontStyle 1803 | 1804 | foreground 1805 | #66d9ef 1806 | 1807 | 1808 | 1809 | name 1810 | GitGutter deleted 1811 | scope 1812 | markup.deleted.git_gutter 1813 | settings 1814 | 1815 | foreground 1816 | #F92672 1817 | 1818 | 1819 | 1820 | name 1821 | GitGutter inserted 1822 | scope 1823 | markup.inserted.git_gutter 1824 | settings 1825 | 1826 | foreground 1827 | #A6E22E 1828 | 1829 | 1830 | 1831 | name 1832 | GitGutter changed 1833 | scope 1834 | markup.changed.git_gutter 1835 | settings 1836 | 1837 | foreground 1838 | #967EFB 1839 | 1840 | 1841 | 1842 | name 1843 | GitGutter ignored 1844 | scope 1845 | markup.ignored.git_gutter 1846 | settings 1847 | 1848 | foreground 1849 | #565656 1850 | 1851 | 1852 | 1853 | name 1854 | GitGutter untracked 1855 | scope 1856 | markup.untracked.git_gutter 1857 | settings 1858 | 1859 | foreground 1860 | #565656 1861 | 1862 | 1863 | 1864 | name 1865 | Nginx path 1866 | scope 1867 | string.other.path.nginx 1868 | settings 1869 | 1870 | foreground 1871 | #fc951e 1872 | 1873 | 1874 | 1875 | 1876 | scope 1877 | kiwilime.separator 1878 | settings 1879 | 1880 | background 1881 | #FFFFFF0F 1882 | fontStyle 1883 | bold 1884 | foreground 1885 | #FFFFFF33 1886 | 1887 | 1888 | 1889 | scope 1890 | kiwilime.scaninfo.project 1891 | settings 1892 | 1893 | fontStyle 1894 | bold 1895 | foreground 1896 | #F92672 1897 | 1898 | 1899 | 1900 | scope 1901 | kiwilime.title 1902 | settings 1903 | 1904 | fontStyle 1905 | 1906 | foreground 1907 | #FD971F 1908 | 1909 | 1910 | 1911 | scope 1912 | kiwilime.filename 1913 | settings 1914 | 1915 | fontStyle 1916 | italic 1917 | foreground 1918 | #66D9EF 1919 | 1920 | 1921 | 1922 | scope 1923 | kiwilime.lineno 1924 | settings 1925 | 1926 | foreground 1927 | #AE81FF 1928 | 1929 | 1930 | 1931 | scope 1932 | kiwilime.matchline 1933 | settings 1934 | 1935 | fontStyle 1936 | italic 1937 | foreground 1938 | #FD971F 1939 | 1940 | 1941 | 1942 | scope 1943 | kiwilime.issue.id 1944 | settings 1945 | 1946 | fontStyle 1947 | bold 1948 | foreground 1949 | #F92672 1950 | 1951 | 1952 | 1953 | scope 1954 | kiwilime.issue.info 1955 | settings 1956 | 1957 | fontStyle 1958 | italic 1959 | foreground 1960 | #F92672DD 1961 | 1962 | 1963 | 1964 | scope 1965 | kiwilime.issue.pattern 1966 | settings 1967 | 1968 | background 1969 | #F92672 1970 | fontStyle 1971 | 1972 | foreground 1973 | #F8F8F0 1974 | 1975 | 1976 | 1977 | scope 1978 | kiwilime.level.high 1979 | settings 1980 | 1981 | foreground 1982 | #F92672 1983 | 1984 | 1985 | 1986 | scope 1987 | kiwilime.level.medium 1988 | settings 1989 | 1990 | foreground 1991 | #F6AA11 1992 | 1993 | 1994 | 1995 | scope 1996 | kiwilime.level.low 1997 | settings 1998 | 1999 | foreground 2000 | #E6DB74 2001 | 2002 | 2003 | 2004 | scope 2005 | kiwilime.level.info 2006 | settings 2007 | 2008 | fontStyle 2009 | underline 2010 | foreground 2011 | #A6E22E 2012 | 2013 | 2014 | 2015 | scope 2016 | kiwilime.banner 2017 | settings 2018 | 2019 | foreground 2020 | #75715E 2021 | 2022 | 2023 | 2024 | scope 2025 | kiwilime.comment 2026 | settings 2027 | 2028 | foreground 2029 | #75715E 2030 | 2031 | 2032 | 2033 | uuid 2034 | 1D07ACC0-832F-11E2-9E96-0800200C9A66 2035 | 2036 | 2037 | -------------------------------------------------------------------------------- /kiwilime/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 1 关于 3 | 4 | `kiwilime` 是一个源码安全审计的 *Sublime Text3* 插件,主要实现以下功能: 5 | 6 | - 调用 `kiwi` 工具对当前 *Forder* 进行源码安全审计扫描 7 | - 高亮显示 `kiwi` 工具的安全扫描报告,并且能够快速跳转到扫描报告中指定的相应代码位置 8 | - 在当前 Forder 中快速搜索当前选中或当前光标所在的单词 9 | - 将可以的代码位置加入到 **review-view** 10 | - 将当前代码跟踪的关键位置记录到 **trace-viw** 11 | - 将无用的代码审计信息删除到 **trash-view** 12 | - 高亮显示当前打开的代码文件中可能存在安全问题的代码位置 13 | - 根据当前单词或当前选中的单词,在当前文件中前后跳转 14 | 15 | 注:*Forder* 为 *Sublime Text3* 打开的 *project*,一般显示在左侧边栏 16 | 17 | 18 | # 2 安装 19 | 20 | **Step 1.** 21 | 22 | 获取 kiwilime 23 | 24 | git clone https://github.com/alpha1e0/kiwi.git 25 | 26 | **Step 2.** 27 | 28 | 打开 Sublime Text 3 package-directory 29 | 30 | Preferences --> Browse packages 31 | 32 | **Step 3.** 33 | 34 | 将 **kiwilime** 目录整个 copy 到 package-directory 35 | 36 | **Setp 4.** 37 | 38 | 下载pt工具(kilime用该工具进行代码搜索)。[下载地址](https://github.com/monochromegane/the_platinum_searcher/releases) 39 | 40 | 41 | -------------------------------------------------------------------------------- /kiwilime/Side Bar.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "-" 4 | }, 5 | { 6 | "caption": "Code analyze", 7 | "command": "run_kiwi", 8 | "args": { 9 | "dirs": [], 10 | "files": [] 11 | } 12 | }, 13 | { 14 | "caption": "Kiwilime code search", 15 | "command": "global_code_search", 16 | "args": { 17 | "dirs": [], 18 | "files": [] 19 | } 20 | }, 21 | { 22 | "caption": "-", "id": "kiwi_commands" 23 | } 24 | ] -------------------------------------------------------------------------------- /kiwilime/issuedef/java: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwilime, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # Issue defination for source.java 9 | #=============================================================================== 10 | 11 | version: 1.0 12 | scope: source.java -------------------------------------------------------------------------------- /kiwilime/issuedef/php: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwilime, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # Issue defination for source.php 9 | #=============================================================================== 10 | 11 | version: 1.0 12 | scope: source.php -------------------------------------------------------------------------------- /kiwilime/issuedef/python: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | 4 | #=============================================================================== 5 | # Kiwilime, Security tool for auditing source code 6 | # Copyright (c) 2016 alpha1e0 7 | # ----------------------------------------------------------------------------- 8 | # Issue defination for source.python 9 | #=============================================================================== 10 | 11 | version: 1.0 12 | scope: source.python 13 | 14 | default-patterns: 15 | - \bos.system\b 16 | - \bos.popen\b 17 | - \bos.popen2\b 18 | - \bos.popen3\b 19 | - \bos.popen4\b 20 | - \bpopen2.popen2\b 21 | - \bpopen2.popen3\b 22 | - \bpopen2.popen4\b 23 | - \bpopen2.Popen3\b 24 | - \bpopen2.Popen4\b 25 | - \bcommands.getoutput\b 26 | - \bcommands.getstatusoutput\b 27 | - \bsubprocess.call\b 28 | - \bsubprocess.Popen\b 29 | - \bsubprocess.check_call\b 30 | - \bsubprocess.check_output\b 31 | - \butils.execute\b 32 | - \butils.execute_with_timeout\b 33 | - \bos.remove\b 34 | - \bos.unlink\b 35 | - \bos.rmdir\b 36 | - \bos.removedirs\b 37 | - \bshutil.rmtree\b 38 | - \bpickle/cPickle\b 39 | - \bpickle.load\b 40 | - \bpickle.dump\b 41 | - \byaml.load\b 42 | - \byaml.dump\b 43 | - \beval\b 44 | - \beval_r\b 45 | - \bexec\b 46 | - \bexecfile\b 47 | - \["']select.+from 48 | - \["']insert.+into 49 | - \["']update.+set 50 | - \["']drop 51 | - \["']delete.+from 52 | 53 | # Add user defined patterns here 54 | user-patterns: 55 | # - somepattern 56 | -------------------------------------------------------------------------------- /kiwilime/kiwilime.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | 4 | ''' 5 | Kiwilime, a sublime plugin for finding security bugs. 6 | -------------------------------------------------------------------------------- 7 | Copyright (c) 2016 alpha1e0 8 | ''' 9 | 10 | 11 | import os 12 | import re 13 | import yaml 14 | import time 15 | import threading 16 | import subprocess 17 | 18 | import sublime 19 | import sublime_plugin 20 | 21 | 22 | BTLIME_SCOPE = 'kiwilime.info' 23 | BTLIME_SETTING_FILE = 'kiwilime.sublime-settings' 24 | BTLIME_SYNTAX_FILE = "Packages/kiwilime/kiwilime.sublime-syntax" 25 | DEFAULT_ENCODING = "utf-8" 26 | 27 | 28 | 29 | class FileError(Exception): 30 | pass 31 | 32 | 33 | class YamlConf(object): 34 | ''' 35 | Yaml configure file loader 36 | ''' 37 | def __new__(cls, path): 38 | try: 39 | _file = open(path,"r") 40 | result = yaml.load(_file) 41 | except IOError: 42 | raise FileError( 43 | "Loading yaml file '{0}' failed, read file failed".format(path)) 44 | except yaml.YAMLError as error: 45 | raise FileError( 46 | "Loading yaml file '{0}' failed, yaml error, reason: '{1}'"\ 47 | .format(path,str(error))) 48 | except Exception as error: 49 | raise FileError("Loading yaml file '{0}' failed, reason: {1}"\ 50 | .format(path,str(error))) 51 | 52 | return result 53 | 54 | 55 | 56 | def run_in_thread(func): 57 | ''' 58 | 线程运行函数修饰器 59 | ''' 60 | def thread_func(*args, **kwargs): 61 | def run(): 62 | r = func(*args, **kwargs) 63 | 64 | t = threading.Thread(target=run) 65 | t.start() 66 | 67 | return thread_func 68 | 69 | 70 | 71 | class current(object): 72 | ''' 73 | current类,用于获取当前信息,例如: 74 | 当前光标所在region 75 | 当前光标的单词 76 | ''' 77 | @classmethod 78 | def point(cls, view): 79 | ''' 80 | 获取当前view中光标所在偏移(相对于文本文件中第一个字符) 81 | ''' 82 | return view.sel()[0].a 83 | 84 | 85 | @classmethod 86 | def region(cls, view): 87 | ''' 88 | 获取当前view的当前region信息([start_index, end_index]) 89 | ''' 90 | return view.sel()[0] 91 | 92 | 93 | @classmethod 94 | def wordregion(cls, view): 95 | ''' 96 | 获取当前view选中的单词的region,或者当前view光标所在单词的region 97 | ''' 98 | sel = view.sel()[0] 99 | if sel.a == sel.b: 100 | return view.word(sel) 101 | else: 102 | return sel 103 | 104 | 105 | @classmethod 106 | def regions(cls, view): 107 | ''' 108 | 获取当前view所有regions 109 | ''' 110 | return [r for r in view.sel()] 111 | 112 | 113 | @classmethod 114 | def scope(cls, view): 115 | ''' 116 | 获取当前view的scope信息,例如:source.python 117 | ''' 118 | return view.scope_name(view.sel()[0].a) 119 | 120 | 121 | @classmethod 122 | def word(cls, view): 123 | ''' 124 | 获取当前选中的单词 125 | ''' 126 | return view.substr(cls.wordregion(view)) 127 | 128 | 129 | @classmethod 130 | def rowcol(cls, view): 131 | point = view.sel()[0].a 132 | return view.rowcol(point) 133 | 134 | 135 | @classmethod 136 | def projdir(cls, view): 137 | ''' 138 | 获取当前文件所在的forder 139 | ''' 140 | window = view.window() 141 | 142 | for folder in window.folders(): 143 | if view.file_name().startswith(folder): 144 | return folder 145 | 146 | 147 | @classmethod 148 | def filename(cls, view): 149 | return view.file_name() 150 | 151 | 152 | @classmethod 153 | def filepath(cls, view): 154 | return os.path.dirname(view.file_name()) 155 | 156 | 157 | @classmethod 158 | def filecontent(cls, view): 159 | return view.substr(sublime.Region(0,view.size())) 160 | 161 | 162 | @classmethod 163 | def pkgpath(cls): 164 | return os.path.dirname(__file__) 165 | 166 | 167 | @classmethod 168 | def cache_dir(cls, view): 169 | ''' 170 | 获取临时文件、缓存文件保存的目录 171 | ''' 172 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 173 | cachedir = settings.get("cache_directory_name", ".kiwilime-cache") 174 | projdir = cls.projdir(view) 175 | 176 | return os.path.join(projdir, cachedir) 177 | 178 | 179 | @classmethod 180 | def review_file(cls, view): 181 | ''' 182 | 获取review_file 183 | ''' 184 | projdir = cls.projdir(view) 185 | 186 | return CacheFile(projdir, 'review') 187 | 188 | 189 | @classmethod 190 | def trace_file(cls, view): 191 | ''' 192 | 获取trace_file 193 | ''' 194 | projdir = cls.projdir(view) 195 | 196 | return CacheFile(projdir, 'trace') 197 | 198 | 199 | @classmethod 200 | def trash_file(cls, view): 201 | ''' 202 | 获取trash_file 203 | ''' 204 | projdir = cls.projdir(view) 205 | 206 | return CacheFile(projdir, 'trash') 207 | 208 | 209 | 210 | def show_error(msg): 211 | ''' 212 | 弹窗显示错误信息 213 | ''' 214 | sublime.error_message(str(msg)) 215 | 216 | 217 | def show_status(msg): 218 | ''' 219 | 状态栏显示信息 220 | ''' 221 | sublime.active_window().status_message(str(msg)) 222 | 223 | 224 | def is_kiwilime_info(view): 225 | ''' 226 | 判断是否为scope=kiwilime.info 227 | ''' 228 | return view.match_selector(current.point(view), "kiwilime.info") 229 | 230 | 231 | def get_line(view, point): 232 | ''' 233 | Get the line from view:point 234 | @returns: 235 | (line_content, line_region, line_left_point) 236 | note that, the line_content dose not contains newline character, but 237 | the line_region contains 238 | ''' 239 | region = view.full_line(point) 240 | content = view.substr(region) 241 | 242 | return content.rstrip("\n"), region, region.a 243 | 244 | 245 | def _match_line_number(line): 246 | line_number_pattern = re.compile(r"^(\d+)[-:]") 247 | match = line_number_pattern.match(line) 248 | 249 | return int(match.group(1)) if match else None 250 | 251 | def _match_file_name(line): 252 | file_name_pattern = re.compile(r"^@(.+)") 253 | match = file_name_pattern.match(line) 254 | 255 | return match.group(1) if match else None 256 | 257 | 258 | def get_file_location(view, point): 259 | ''' 260 | Get the file location from the kiwilime info entry. 261 | For example: 262 | @/aaa/bbb/xxx.py 263 | 23- for i in range(100) 264 | 24: i = i**2 265 | 25- print i 266 | will get the file location: /aaa/bbb/xxx.py:24 267 | ''' 268 | line_content, _, line_point = get_line(view, point) 269 | if not line_content: 270 | return None, None 271 | 272 | file_name = _match_file_name(line_content) 273 | if file_name: 274 | return file_name, 0 275 | 276 | lineno = _match_line_number(line_content) 277 | if not lineno: 278 | return None, None 279 | 280 | current_point = line_point-1 281 | while current_point >= 0: 282 | line_content, _, line_point = get_line(view, current_point) 283 | 284 | file_name = _match_file_name(line_content) 285 | if not file_name: 286 | current_point = line_point-1 287 | continue 288 | else: 289 | return file_name, lineno 290 | 291 | 292 | def get_info_entry(view, point): 293 | ''' 294 | Get the kiwilime info entry from the kiwilime info entry. 295 | @returns: 296 | The entry string, For example: 297 | 298 | @/aaa/bbb/xxx.py 299 | 23- for i in range(100) 300 | 24: i = i**2 301 | 25- print i 302 | ''' 303 | result = "" 304 | line_content, line_region, line_point = get_line(view, point) 305 | 306 | if not line_content: 307 | return line_content, line_region 308 | 309 | before = [] 310 | current_point = line_point - 1 311 | while current_point >= 0: 312 | current_content, _, current_point = get_line(view, current_point) 313 | # if meet blank line break 314 | if not current_content: 315 | break 316 | 317 | before.insert(0, current_content) 318 | current_point = current_point-1 319 | 320 | before_point = current_point+1 321 | 322 | after = [] 323 | current_point = line_region.b 324 | while current_point < view.size(): 325 | current_content, current_region, current_point = \ 326 | get_line(view, current_point) 327 | # if meet blank line break 328 | if not current_content: 329 | break 330 | 331 | after.append(current_content) 332 | current_point = current_region.b 333 | 334 | after_point = current_region.b 335 | 336 | result = "\n".join(before) + "\n" + (line_content+"\n") + "\n".join(after) 337 | 338 | return result, sublime.Region(before_point, after_point) 339 | 340 | 341 | def get_projdir_from_entry(view, point): 342 | file_name, _ = get_file_location(view, point) 343 | if not file_name: 344 | return None 345 | 346 | for folder in view.window().folders(): 347 | if file_name.startswith(folder): 348 | return folder 349 | 350 | return None 351 | 352 | 353 | def get_code_context(view, point, ctxs=2): 354 | row = lambda p: view.rowcol(p)[0]+1 355 | 356 | line_content, line_region, line_point = get_line(view, point) 357 | 358 | word = current.word(view) 359 | rowno = row(line_point) 360 | context = [(rowno, line_content)] 361 | 362 | current_point = line_point 363 | for i in range(ctxs): 364 | current_point = current_point - 1 365 | current_content, _, current_point = get_line(view, current_point) 366 | context.insert(0, (row(current_point), current_content)) 367 | 368 | current_region = line_region 369 | for i in range(ctxs): 370 | current_point = current_region.b + 1 371 | current_content, current_region, _ = get_line(view, current_point) 372 | context.append((row(current_point), current_content)) 373 | 374 | return context, rowno, word 375 | 376 | 377 | def get_formated_code_context(view, point, ctxs=2): 378 | context, rowno, word = get_code_context(view, point, ctxs) 379 | largest_lineno = context[-1][0] 380 | 381 | result = "\n@{1}\n".format(word, view.file_name()) 382 | 383 | no_fmt = "{0:>" + str(len(str(largest_lineno))) + "}" 384 | fmt = "{lineno}{spliter} {content}\n" 385 | 386 | for line in context: 387 | spliter = "-" if line[0]!=rowno else ":" 388 | result = result + fmt.format(lineno = no_fmt.format(line[0]), 389 | spliter = spliter, 390 | content = line[1]) 391 | 392 | return result + "\n" 393 | 394 | 395 | 396 | def load_patterns(scope): 397 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 398 | issuedef = settings.get("issuedef", None) 399 | if not issuedef: 400 | return [] 401 | 402 | for entry in issuedef: 403 | if entry['scope'] in scope: 404 | idfile = os.path.join(current.pkgpath(), 'issuedef', 405 | entry['filename']) 406 | break 407 | else: 408 | idfile = None 409 | 410 | if idfile: 411 | config = YamlConf(idfile) 412 | else: 413 | return [] 414 | 415 | patterns = [] 416 | if config: 417 | default_patterns = config.get('default-patterns',[]) 418 | user_patterns = config.get('user-patterns',[]) 419 | if isinstance(default_patterns, list): 420 | for pattern in default_patterns: 421 | patterns.append(re.compile(pattern)) 422 | 423 | if isinstance(user_patterns, list): 424 | for pattern in user_patterns: 425 | patterns.append(re.compile(pattern)) 426 | 427 | return patterns 428 | 429 | 430 | def run_cmd(cmd): 431 | if isinstance(cmd, list): 432 | cmd = [str(c) for c in cmd] 433 | 434 | output = "" 435 | shell = True if sublime.platform() == 'windows' else False 436 | try: 437 | output = subprocess.check_output(cmd, shell=shell) 438 | except Exception as error: 439 | show_error(str(error)) 440 | 441 | return output 442 | 443 | 444 | def search(patterns, directories, includes, excludes): 445 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 446 | ptcmd = settings.get("code_search_command") 447 | 448 | if ptcmd: 449 | return pt_search(ptcmd, patterns, directories, includes, excludes) 450 | else: 451 | return simple_search(patterns, directories, includes, exculdes) 452 | 453 | 454 | def _build_pt_command(ptcmd, pattern, directory, ctxs): 455 | if sublime.platform() == 'windows': 456 | ctxs_lable = "/C" 457 | else: 458 | ctxs_lable = "-C" 459 | 460 | return [ptcmd, pattern, directory, ctxs_lable, ctxs] 461 | 462 | 463 | def _format_pt_result(ptresult, pattern): 464 | result = "" 465 | 466 | entry = "" 467 | current_file = "" 468 | 469 | line_pattern = re.compile(r"^(.+):(\d+[:-])(.*)$") 470 | 471 | if isinstance(ptresult, bytes): 472 | lines = ptresult.decode().split("\n") 473 | else: 474 | lines = ptresult.split("\n") 475 | 476 | for line in lines: 477 | line = line.strip() 478 | if not line: 479 | continue 480 | 481 | match = line_pattern.match(line) 482 | if not match: 483 | continue 484 | 485 | filename, lineno, content = match.group(1), \ 486 | match.group(2), " {0}".format(match.group(3)) 487 | 488 | if current_file != filename: 489 | current_file = filename 490 | result = result + "\n" + entry 491 | entry = "\n".format(pattern) 492 | entry = entry + "@{0}\n".format(current_file) 493 | 494 | entry = entry + lineno + content + "\n" 495 | 496 | result = result + "\n" + entry 497 | 498 | return result 499 | 500 | 501 | 502 | def pt_search(ptcmd, patterns, directories, includes, excludes): 503 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 504 | ctxs = settings.get("result_context", 2) 505 | 506 | search_result = "" 507 | for pattern in patterns: 508 | for directory in directories: 509 | cmd = _build_pt_command(ptcmd, pattern, directory, ctxs) 510 | pt_output = run_cmd(cmd) 511 | search_result = search_result + "\n" + _format_pt_result(pt_output, 512 | pattern) 513 | 514 | return "#!kiwilime" + search_result 515 | 516 | 517 | 518 | def simple_search(patterns, directories, includes, exculdes): 519 | show_error("Can not find code search tool 'pt'," 520 | " and simple mode is not support now") 521 | 522 | 523 | class CacheFile(object): 524 | def __init__(self, projdir, cachetype): 525 | ''' 526 | manage class for cache-file 527 | @params: 528 | projdir: the project directory 529 | cachetype: cache-file type, should be one of 530 | ['review','trace','trash'] 531 | ''' 532 | if cachetype not in ['review','trace','trash']: 533 | raise FileError("cache-file type error.") 534 | 535 | self._header = "#!kiwilime\n\n## This is the view for {0}\n\n".format( 536 | cachetype) 537 | 538 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 539 | cachedir = settings.get('cache_directory_name', ".kiwilime-cache") 540 | self._cachepath = os.path.join(projdir, cachedir) 541 | if not os.path.exists(self._cachepath): 542 | os.makedirs(self._cachepath) 543 | 544 | self._cachefile = os.path.join(self._cachepath, cachetype) 545 | if not os.path.exists(self._cachefile): 546 | with open(self._cachefile, "w") as _file: 547 | _file.write(self._header) 548 | 549 | 550 | def append(self, data): 551 | with open(self._cachefile, "a", encoding=DEFAULT_ENCODING) as _file: 552 | _file.write(str(data)) 553 | 554 | 555 | 556 | 557 | def analyze(view, btcmd, projdir, cachedir): 558 | if btcmd: 559 | bt_analyze(view, btcmd, projdir, cachedir) 560 | else: 561 | simple_analyze(view, projdir, cachedir) 562 | 563 | 564 | 565 | @run_in_thread 566 | def bt_analyze(view, btcmd, projdir, cachedir): 567 | projname = os.path.split(projdir)[-1] 568 | cachename = os.path.split(cachedir)[-1] 569 | result_file = os.path.join(cachedir, projname+".kiwi") 570 | 571 | if not os.path.exists(cachedir): 572 | os.mkdir(cachedir) 573 | 574 | cmd = [btcmd, projdir, "--exclude", cachename, 575 | "-o", result_file] 576 | cmd = " ".join(cmd) 577 | 578 | show_status("Running kiwi ...") 579 | 580 | shell = True 581 | try: 582 | output = subprocess.check_output(cmd, shell=shell) 583 | except Exception as error: 584 | show_error(str(error)) 585 | return 586 | 587 | show_status("Bugtrack scan finished.") 588 | 589 | view.window().open_file(result_file) 590 | 591 | 592 | @run_in_thread 593 | def simple_analyze(view, projdir, cachedir): 594 | show_error("Can not find code search tool 'kiwi'," 595 | " and simple mode is not support now") 596 | 597 | 598 | 599 | class RunBugtrackCommand(sublime_plugin.TextCommand): 600 | def run(self, edit, **args): 601 | projdir = args['dirs'][0] 602 | 603 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 604 | btcmd = settings.get("kiwi_command", None) 605 | 606 | cachedir = settings.get("cache_directory_name", ".kiwilime-cache") 607 | cachedir = os.path.join(projdir, cachedir) 608 | 609 | analyze(self.view, btcmd, projdir, cachedir) 610 | 611 | 612 | 613 | 614 | class GlobalCodeSearchCommand(sublime_plugin.TextCommand): 615 | def run(self, edit, **args): 616 | return 617 | 618 | 619 | #===== 620 | 621 | 622 | 623 | class JumpLocationCommand(sublime_plugin.TextCommand): 624 | @property 625 | def _is_kiwilime_view(self): 626 | scope_name = self.view.scope_name(0) 627 | return True if BTLIME_SCOPE in scope_name else False 628 | 629 | def run(self, edit, **args): 630 | if not self._is_kiwilime_view: 631 | return 632 | 633 | point = current.point(self.view) 634 | 635 | file_name, lineno = get_file_location(self.view, point) 636 | if not file_name: 637 | return None 638 | 639 | if os.path.exists(file_name): 640 | file_loc = "{0}:{1}".format(file_name, lineno) 641 | self.view.window().open_file(file_loc, sublime.ENCODED_POSITION) 642 | else: 643 | show_error("File '{0}'' doses not exists.".format( 644 | file_name)) 645 | 646 | 647 | 648 | class SendtoTrashCommand(sublime_plugin.TextCommand): 649 | @property 650 | def _is_kiwilime_view(self): 651 | scope_name = self.view.scope_name(0) 652 | return True if BTLIME_SCOPE in scope_name else False 653 | 654 | def run(self, edit, **args): 655 | if not self._is_kiwilime_view: 656 | return 657 | 658 | content, region = get_info_entry(self.view, current.point(self.view)) 659 | if not content: 660 | return 661 | 662 | try: 663 | trash_file = current.trash_file(self.view) 664 | except AttributeError: 665 | projdir = get_projdir_from_entry(self.view, 666 | current.point(self.view)) 667 | if not projdir: 668 | return 669 | trash_file = CacheFile(projdir, "trash") 670 | 671 | trash_file.append("\n"+content+"\n") 672 | 673 | self.view.erase(edit, region) 674 | 675 | 676 | 677 | class SendtoReviewCommand(sublime_plugin.TextCommand): 678 | @property 679 | def _is_kiwilime_view(self): 680 | scope_name = self.view.scope_name(0) 681 | return True if BTLIME_SCOPE in scope_name else False 682 | 683 | def run(self, edit, **args): 684 | if not self._is_kiwilime_view: 685 | return 686 | 687 | content, region = get_info_entry(self.view, 688 | current.point(self.view)) 689 | if not content: 690 | return 691 | 692 | try: 693 | review_file = current.review_file(self.view) 694 | except AttributeError: 695 | projdir = get_projdir_from_entry(self.view, 696 | current.point(self.view)) 697 | if not projdir: 698 | return 699 | review_file = CacheFile(projdir, 'review') 700 | 701 | review_file.append("\n"+content+"\n") 702 | 703 | 704 | 705 | 706 | class ShowIssueCommand(sublime_plugin.TextCommand): 707 | ISSUE_KEY = "kiwilime-issue" 708 | ISSUES_COPE = "invalid.illegal" 709 | 710 | def __init__(self, *args, **kwargs): 711 | self._current_region_index = 0 712 | self._issue_regions = [] 713 | 714 | super(ShowIssueCommand, self).__init__(*args, **kwargs) 715 | 716 | 717 | def run(self, edit, **args): 718 | if not self._is_showing: 719 | self._issue_regions = self._get_issue_regions() 720 | 721 | self.view.add_regions(self.ISSUE_KEY, self._issue_regions, 722 | self.ISSUES_COPE) 723 | 724 | self.view.show(self._issue_regions[0]) 725 | self.view.settings().set("is_finding", True) 726 | else: 727 | region = self._get_next_region() 728 | self.view.show(region) 729 | 730 | 731 | def _get_issue_regions(self): 732 | result = [] 733 | 734 | scope = current.scope(self.view) 735 | patterns = load_patterns(scope) 736 | content = current.filecontent(self.view) 737 | 738 | for pattern in patterns: 739 | for match in pattern.finditer(content): 740 | if self.view.match_selector(match.start(), "comment"): 741 | continue 742 | 743 | result.append(sublime.Region(match.start(),match.end())) 744 | 745 | return result 746 | 747 | 748 | def _get_next_region(self): 749 | index = self._current_region_index + 1 750 | if index == len(self._issue_regions): 751 | index = 0 752 | 753 | self._current_region_index = index 754 | return self._issue_regions[index] 755 | 756 | 757 | @property 758 | def _is_showing(self): 759 | return True if self.view.get_regions(self.ISSUE_KEY) else False 760 | 761 | 762 | class FindAllCommand(sublime_plugin.TextCommand): 763 | FINDING_KEY = "finding" 764 | FINDING_SCOPE = "invalid.deprecated" 765 | 766 | def __init__(self, *args, **kwargs): 767 | self._current_region_index = 0 768 | self._match_regions = [] 769 | 770 | super(FindAllCommand, self).__init__(*args, **kwargs) 771 | 772 | 773 | def run(self, edit, **args): 774 | forward = args.get('forward', False) 775 | 776 | if not self._is_current_finding: 777 | self.view.erase_regions(self.FINDING_KEY) 778 | self.view.settings().set("is_finding", False) 779 | 780 | current_region = current.wordregion(self.view) 781 | match_word = current.word(self.view) 782 | 783 | self._match_regions, self._current_region_index = \ 784 | self._get_match_info(current_region, match_word) 785 | self._draw_regions(self._match_regions) 786 | 787 | self.view.settings().set("is_finding", True) 788 | else: 789 | if forward: 790 | region = self._get_pre_region() 791 | else: 792 | region = self._get_next_region() 793 | 794 | self.view.show(region) 795 | 796 | 797 | def _get_match_info(self, current_region, match_word): 798 | ''' 799 | 获取所有匹配位置 800 | ''' 801 | index = 0 802 | all_match_regions = self.view.find_all(match_word, sublime.LITERAL) 803 | 804 | for region in all_match_regions: 805 | if region == current_region: 806 | break 807 | index += 1 808 | 809 | if index >= len(all_match_regions): 810 | index = len(all_match_regions) 811 | 812 | return all_match_regions, index 813 | 814 | 815 | def _get_pre_region(self): 816 | index = self._current_region_index - 1 817 | if index == -1: 818 | index = len(self._match_regions) - 1 819 | 820 | self._current_region_index = index 821 | return self._match_regions[index] 822 | 823 | 824 | def _get_next_region(self): 825 | index = self._current_region_index + 1 826 | if index == len(self._match_regions): 827 | index = 0 828 | 829 | self._current_region_index = index 830 | return self._match_regions[index] 831 | 832 | 833 | def _draw_regions(self, regions): 834 | self.view.add_regions(self.FINDING_KEY, regions, self.FINDING_SCOPE) 835 | 836 | 837 | @property 838 | def _is_current_finding(self): 839 | regions = self.view.get_regions(self.FINDING_KEY) 840 | 841 | if regions: 842 | return True 843 | 844 | return False 845 | 846 | 847 | 848 | class FindFirstCommand(sublime_plugin.TextCommand): 849 | FINDING_KEY = "finding" 850 | FINDING_SCOPE = "invalid.deprecated" 851 | 852 | def run(self, edit, **args): 853 | if not self._is_current_finding: 854 | self.view.erase_regions(self.FINDING_KEY) 855 | self.view.settings().set("is_finding", False) 856 | 857 | current_region = current.wordregion(self.view) 858 | current_word = current.word(self.view) 859 | first_region = self.view.find(current_word, 0, sublime.LITERAL) 860 | 861 | self._draw_regions([current_region, first_region]) 862 | 863 | self.view.settings().set("is_finding", True) 864 | self.view.show(first_region) 865 | 866 | return 867 | 868 | visible_region = self.view.visible_region() 869 | for region in self._match_regions: 870 | if not visible_region.contains(region): 871 | self.view.show(region) 872 | return 873 | 874 | 875 | def _draw_regions(self, regions): 876 | self.view.add_regions(self.FINDING_KEY, regions, self.FINDING_SCOPE) 877 | 878 | 879 | @property 880 | def _match_regions(self): 881 | return self.view.get_regions(self.FINDING_KEY) 882 | 883 | 884 | @property 885 | def _is_current_finding(self): 886 | regions = self.view.get_regions(self.FINDING_KEY) 887 | 888 | if regions: 889 | return True 890 | 891 | return False 892 | 893 | 894 | 895 | class CleanFindingsCommand(sublime_plugin.TextCommand): 896 | def run(self, edit, **args): 897 | self.view.erase_regions(FindAllCommand.FINDING_KEY) 898 | self.view.erase_regions(ShowIssueCommand.ISSUE_KEY) 899 | self.view.settings().set("is_finding", False) 900 | 901 | 902 | 903 | class RecordtoTraceCommand(sublime_plugin.TextCommand): 904 | def run(self, edit, **args): 905 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 906 | ctxs = settings.get("result_context", 2) 907 | 908 | context = get_formated_code_context(self.view, 909 | current.point(self.view),ctxs) 910 | 911 | trace_file = current.trace_file(self.view) 912 | trace_file.append("\n"+context+"\n") 913 | 914 | 915 | 916 | class RecordtoReviewCommand(sublime_plugin.TextCommand): 917 | def run(self, edit, **args): 918 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 919 | ctxs = settings.get("result_context", 2) 920 | 921 | context = get_formated_code_context(self.view, 922 | current.point(self.view),ctxs) 923 | 924 | review_file = current.review_file(self.view) 925 | review_file.append("\n"+context+"\n") 926 | 927 | 928 | 929 | class CodeSearchCommand(sublime_plugin.TextCommand): 930 | def run(self, edit, **args): 931 | word = current.word(self.view) 932 | directory = current.projdir(self.view) 933 | 934 | search_result = search([word], [directory], None, None) 935 | 936 | view = self.view.window().new_file() 937 | view.insert(edit, 0, search_result) 938 | view.set_name("search {0}".format(word)) 939 | view.set_syntax_file(BTLIME_SYNTAX_FILE) 940 | 941 | 942 | 943 | class GotoDefinationCommand(sublime_plugin.TextCommand): 944 | def run(self, edit, **args): 945 | return 946 | 947 | 948 | class OpenIssueDefCommand(sublime_plugin.TextCommand): 949 | def run(self, edit, **args): 950 | settings = sublime.load_settings(BTLIME_SETTING_FILE) 951 | issuedef = settings.get("issuedef", None) 952 | if not issuedef: 953 | return 954 | 955 | for entry in issuedef: 956 | idfile = os.path.join(current.pkgpath(), 'issuedef', 957 | entry['filename']) 958 | self.view.window().open_file(idfile) 959 | -------------------------------------------------------------------------------- /kiwilime/kiwilime.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "cache_directory_name": ".kiwilime-cache", 3 | "kiwi_command": "/Users/apple/pentest/kiwi/kiwi/kiwi.py", 4 | "code_search_command": "/Users/apple/bin/pt", 5 | "result_context": 2, 6 | "issuedef": [ 7 | { 8 | "scope": "source.python", 9 | "filename": "python", 10 | "exts": ["py", "py3", "pyw", "pyi", "rpy", "cpy", "SConstruct", "Sconstruct", "sconstruct", "SConscript", "gyp", "gypi", "Snakefile", "wscript"] 11 | }, 12 | { 13 | "scope": "source.java", 14 | "filename": "java", 15 | "exts": ["java", "bsh"] 16 | }, 17 | { 18 | "scope": "embedding.php", 19 | "filename": "php", 20 | "exts": ["php", "php3", "php4", "php5", "php7", "phps", "phpt", "phtml"] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /kiwilime/kiwilime.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # See http://www.sublimetext.com/docs/3/syntax.html 4 | name: kiwilime 5 | file_extensions: 6 | - kiwi 7 | first_line_match: ^#!kiwilime 8 | scope: kiwilime.info 9 | 10 | contexts: 11 | main: 12 | - match: \+-- 13 | push: banner 14 | 15 | - match: ^\| 16 | push: banner 17 | 18 | - match: ^([-=]+)$ 19 | captures: 20 | 1: kiwilime.separator 21 | 22 | 23 | - match: "Scaning <(.+)> at (.+)$" 24 | captures: 25 | 1: kiwilime.scaninfo.project 26 | 2: kiwilime.scaninfo.scantime 27 | 28 | - match: "(Found sensitive files as follows:)" 29 | captures: 30 | 1: kiwilime.title 31 | 32 | - match: ^(@.+)$ 33 | captures: 34 | 1: kiwilime.filename 35 | 36 | - match: ^(\d+)- 37 | captures: 38 | 1: kiwilime.lineno 39 | 40 | - match: "^(\\d+)\\:\\s(.*)$" 41 | captures: 42 | 1: kiwilime.lineno 43 | 2: kiwilime.matchline 44 | 45 | - match: "(Found security issues as follows:)" 46 | captures: 47 | 1: kiwilime.title 48 | 49 | - match: ^\[(\w+):(.+)\]$ 50 | captures: 51 | 1: kiwilime.issue.id 52 | 2: kiwilime.issue.info 53 | 54 | - match: <(Match):([^>]+)> 55 | captures: 56 | 1: kiwilime.issue.label 57 | 2: kiwilime.issue.pattern 58 | 59 | - match: <(Severity|Confidence):(High)> 60 | captures: 61 | 1: kiwilime.issue.label 62 | 2: kiwilime.level.high 63 | 64 | - match: <(Severity|Confidence):(Medium)> 65 | captures: 66 | 1: kiwilime.issue.label 67 | 2: kiwilime.level.medium 68 | 69 | - match: <(Severity|Confidence):(Low)> 70 | captures: 71 | 1: kiwilime.issue.label 72 | 2: kiwilime.level.low 73 | 74 | - match: <(Severity|Confidence):(Info)> 75 | captures: 76 | 1: kiwilime.issue.label 77 | 2: kiwilime.level.info 78 | 79 | - match: "(#.*)$" 80 | captures: 81 | 1: kiwilime.comment 82 | 83 | - match: "(Statistics information:)" 84 | captures: 85 | 1: kiwilime.title 86 | 87 | - match: ^(High:\s\d+) 88 | captures: 89 | 1: kiwilime.level.high 90 | 91 | - match: ^(Medium:\s\d+) 92 | captures: 93 | 1: kiwilime.level.medium 94 | 95 | - match: ^(Low:\s\d+) 96 | captures: 97 | 1: kiwilime.level.low 98 | 99 | - match: ^(Info:\s\d+) 100 | captures: 101 | 1: kiwilime.level.info 102 | 103 | banner: 104 | - meta_scope: kiwilime.banner 105 | - match: --\+ 106 | pop: true 107 | - match: \|$ 108 | pop: true 109 | -------------------------------------------------------------------------------- /screenshots/db_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha1e0/kiwi/5215cf7aa7c83c7dde8c0c5e5bbc2d1099a3e93f/screenshots/db_report.png -------------------------------------------------------------------------------- /screenshots/html_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha1e0/kiwi/5215cf7aa7c83c7dde8c0c5e5bbc2d1099a3e93f/screenshots/html_report.png -------------------------------------------------------------------------------- /screenshots/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha1e0/kiwi/5215cf7aa7c83c7dde8c0c5e5bbc2d1099a3e93f/screenshots/run.png --------------------------------------------------------------------------------