如果你喜欢我的文章,欢迎关注公众号:安全女巫
转载请注明出处:https://mp.weixin.qq.com/s/uVFCbaKDYeF9zmVdVXDnUg
前面使用GPT-4对部分代码进行漏洞审计,后面使用GPT-3对git存储库进行对比。最终结果仅供大家在chatgpt在对各类代码分析能力参考,其中存在误报问题,不排除因本人训练模型存在问题导致,欢迎大家对误报结果进行留言,我会第一时间跟进处理~
分析漏洞数量总体占比:
简介
近年来,ChatGPT 已成为互联网新宠,长期以来一直对现代人工智能的潜在影响持怀疑态度的网络安全行业似乎也注意到了这一点,因为他们担心 ChatGPT 可能会被资源有限、技术知识为零的黑客滥用。
目前人工智能和机器学习领域取得了巨大的发展,如此便开辟了一个新的可能性领域-基于 AI 的代码分析,如何使用 AI 模型来检测代码中的安全漏洞尤为惹人关注。
文中使用 OpenAI 的 GPT-4对部分代码进行分析,文末针对GPT-3 如何在包含 129 个易受攻击文件的代码存储库中查找安全漏洞。如果大家感兴趣,我会单独为大家更新哦!
如何运行
GPT-4使用Plus会员版本,跟官网同配置,可处理长代码。(PS:若有朋友需要开通PLus会员,私信留言,将提供保姆级教程和邀请码,能优惠购买哦~)
GPT-3变体(text-davinci-003)具有4000个标记的上下文窗口,大约为3000个英文单词。这意味着它最多可以处理几百行代码的请求,无法一次性处理整个代码库。
为了解决这个问题,只能使用GPT-3扫描单文件。这意味着GPT-3难以找到由多个代码文件交互引起的安全漏洞,除非进行足够多的提示词引导。特别是当源代码使用常见的库,如express.js、Flask、Python标准库、C标准库等时。可能GPT-3有相关记录。在本文分析的代码中,GPT-3对导入库有相关记录就能够准确地检测到安全漏洞,而无需检查任何导入库的代码。
公平地说,我怀疑许多现有的商业漏洞扫描工具在进行静态分析时实际上并不检查导入库的代码,因此这与市场上某些工具的工作方式并没有太大的不同。
被分析的代码存储库中的每个文件夹都以一种安全漏洞命名,并包含带有示例代码的文件,包含一个或多个漏洞。其中一些文件包含琐碎的代码,但许多是您可能会在生产代码库中遇到同样的代码片段(注意:尽管它们仍然是片段,因此缺乏更大代码库的上下文)。存储库中每个文件夹的 README.md 包含 GPT-3 对该文件夹中所有文件的安全漏洞的分析。
https://github.com/chris-koch-penn/gpt3_security_vulnerability_scanner
开篇我是用GPT-4分析,后面代码将会用GPT-3的进行对比。
1》代码注入漏洞检测
PHP代码片段1(tarlogic-ex2.php):
检测结果:
结果还是很详细。
2》Flask反序列化漏洞
Python代码(pickle2.py):
GPT-4对长代码分析能力:
from __future__ import unicode_literals
from flask import Flask, request, make_response, redirect, url_for, session
from flask import render_template, flash, redirect, url_for, request
from werkzeug.security import safe_str_cmp
from base64 import b64decode as b64d
from base64 import b64encode as b64e
from hashlib import sha256
from cStringIO import StringIO
import random
import string
import os
import sys
import subprocess
import commands
import pickle
import cPickle
import marshal
import os.path
import filecmp
import glob
import linecache
import shutil
import dircache
import io
import timeit
import popen2
import code
import codeop
import pty
import posixfile
SECRET_KEY = 'you will never guess'
if not os.path.exists('.secret'):
with open(".secret", "w") as f:
secret = ''.join(random.choice(string.ascii_letters + string.digits)
for x in range(4))
f.write(secret)
with open(".secret", "r") as f:
cookie_secret = f.read().strip()
app = Flask(__name__)
app.config.from_object(__name__)
black_type_list = [eval, execfile, compile, open, file, os.system, os.popen, os.popen2, os.popen3, os.popen4, os.fdopen, os.tmpfile, os.fchmod, os.fchown, os.open, os.openpty, os.read, os.pipe, os.chdir, os.fchdir, os.chroot, os.chmod, os.chown, os.link, os.lchown, os.listdir, os.lstat, os.mkfifo, os.mknod, os.access, os.mkdir, os.makedirs, os.readlink, os.remove, os.removedirs, os.rename, os.renames, os.rmdir, os.tempnam, os.tmpnam, os.unlink, os.walk, os.execl, os.execle, os.execlp, os.execv, os.execve, os.dup, os.dup2, os.execvp, os.execvpe, os.fork, os.forkpty, os.kill, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe,
os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe, pickle.load, pickle.loads, cPickle.load, cPickle.loads, subprocess.call, subprocess.check_call, subprocess.check_output, subprocess.Popen, commands.getstatusoutput, commands.getoutput, commands.getstatus, glob.glob, linecache.getline, shutil.copyfileobj, shutil.copyfile, shutil.copy, shutil.copy2, shutil.move, shutil.make_archive, dircache.listdir, dircache.opendir, io.open, popen2.popen2, popen2.popen3, popen2.popen4, timeit.timeit, timeit.repeat, sys.call_tracing, code.interact, code.compile_command, codeop.compile_command, pty.spawn, posixfile.open, posixfile.fileopen]
@app.before_request
def count():
session['cnt'] = 0
@app.route('/')
def home():
remembered_str = 'Hello, here\'s what we remember for you. And you can change, delete or extend it.'
new_str = 'Hello fellow zombie, have you found a tasty brain and want to remember where? Go right here and enter it:'
location = getlocation()
if location == False:
return redirect(url_for("clear"))
return render_template('index.html', txt=remembered_str, location=location)
@app.route('/clear')
def clear():
flash("Reminder cleared!")
response = redirect(url_for('home'))
response.set_cookie('location', max_age=0)
return response
@app.route('/reminder', methods=['POST', 'GET'])
def reminder():
if request.method == 'POST':
location = request.form["reminder"]
if location == '':
flash("Message cleared, tell us when you have found more brains.")
else:
flash("We will remember where you find your brains.")
location = b64e(pickle.dumps(location))
cookie = make_cookie(location, cookie_secret)
response = redirect(url_for('home'))
response.set_cookie('location', cookie)
return response
location = getlocation()
if location == False:
return redirect(url_for("clear"))
return render_template('reminder.html')
class FilterException(Exception):
def __init__(self, value):
super(FilterException, self).__init__(
'The callable object {value} is not allowed'.format(value=str(value)))
class TimesException(Exception):
def __init__(self):
super(TimesException, self).__init__(
'Call func too many times!')
def _hook_call(func):
def wrapper(*args, **kwargs):
session['cnt'] += 1
print session['cnt']
print args[0].stack
for i in args[0].stack:
if i in black_type_list:
raise FilterException(args[0].stack[-2])
if session['cnt'] > 4:
raise TimesException()
return func(*args, **kwargs)
return wrapper
def loads(strs):
reload(pickle)
files = StringIO(strs)
unpkler = pickle.Unpickler(files)
unpkler.dispatch[pickle.REDUCE] = _hook_call(
unpkler.dispatch[pickle.REDUCE])
return unpkler.load()
def getlocation():
cookie = request.cookies.get('location')
if not cookie:
return ''
(digest, location) = cookie.split("!")
if not safe_str_cmp(calc_digest(location, cookie_secret), digest):
flash("Hey! This is not a valid cookie! Leave me alone.")
return False
location = loads(b64d(location))
return location
def make_cookie(location, secret):
return "%s!%s" % (calc_digest(location, secret), location)
def calc_digest(location, secret):
return sha256("%s%s" % (location, secret)).hexdigest()
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5051)
检测结果:
3》命令注入漏洞检测
PHP代码2:
<?php
$rootUname = $_GET['rootUname'];
$array = array();
/* check PHP Safe_Mode is off /
if (ini_get('safe_mode')) {
$array['phpSafeMode'] = '<strong><font>Fail - php safe mode is on - turn it off before you proceed with the installation</strong></font>br/>';
} else {
$array['phpSafeMode'] = '<strong><font>Pass - php safe mode is off</strong></font><br/>';
}
/ Test root account details */
$rootTestCmd1 = 'sudo -S -u ' . $rootUname . ' chmod 0777 /home 2>&1';
exec($rootTestCmd1, $cmdOutput, $err);
$homeDirPerms = substr(sprintf('%o', fileperms('/home')), -4);
if ($homeDirPerms == '0777') {
$array['rootDetails'] = '<strong><font>Pass - root account details are good </strong></font><br/>';
} else {
$array['rootDetails'] = '<strong><font>The root details provided have not passed: ' . $cmdOutput[0] . '</strong></font><br/>';
}
// reset /home dir permissions
$rootTestCmd2 = 'sudo -S -u ' . $rootUname . ' chmod 0755 /home 2>&1';
exec($rootTestCmd2, $cmdOutput, $err);
echo json_encode($array);
分析结果:
4》XML外部实体注入(XXE)攻击漏洞检测
PHP代码3:
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$info = simplexml_import_dom($dom);
$name = $info->name;
$tel = $info->tel;
$email = $info->email;
$password = $info->password;
echo "Sorry, $email is already registered!";
?>
分析结果:
5》XML外部实体注入(XXE)攻击漏洞检测
这代码比较长,大家看代码库中XmlReader_Tests.cs,GPT-4能处理长脚本。具有token长的特点。我就截图给大家看代码长度。
实例1:
这是一个容易受到格式化字符串攻击的简单 C 程序:
`#include <stdio.h>` `int main(int argc, char **argv) {` `printf(argv[1]);` `return 0;` `}`
检测到的漏洞:
1.未经验证的用户输入:程序不检查用户输入的长度,这可能导致缓冲区溢出攻击。
2.格式化字符串漏洞:程序不检查用户输入的格式,可能导致格式化字符串攻击。
实例2:
让我们用高级语言编写的一个不那么简单的程序来试试这个,比如这个包含 Log Forging 漏洞的C# 程序。我个人在开源项目和生产代码库中看到过类似(尽管不那么琐碎)的代码:
检测到漏洞:
日志注入漏洞:代码容易受到日志注入攻击,因为用户输入未经任何清理就被直接记录下来。
实例3:
以下C 程序读取和操作图像。它包含许多安全漏洞,包括越界读取和写入:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Image
{
char header[4];
int width;
int height;
char data[10];
};
int ProcessImage(char* filename){
FILE *fp;
char ch;
struct Image img;
fp = fopen(filename,"r");
if(fp == NULL)
{
printf("\nCan't open file or file doesn't exist.");
exit(0);
}
printf("\n\tHeader\twidth\theight\tdata\t\r\n");
while(fread(&img,sizeof(img),1,fp)>0){
printf("\n\t%s\t%d\t%d\t%s\r\n",img.header,img.width,img.height,img.data);
int size1 = img.width + img.height;
char* buff1=(char*)malloc(size1);
memcpy(buff1,img.data,sizeof(img.data));
free(buff1);
if (size1/2==0){
free(buff1);
}
else{
if(size1 == 123456){
buff1[0]='a';
}
}
int size2 = img.width - img.height+100;
//printf("Size1:%d",size1);
char* buff2=(char*)malloc(size2);
memcpy(buff2,img.data,sizeof(img.data));
int size3= img.width/img.height;
//printf("Size2:%d",size3);
char buff3[10];
char* buff4 =(char*)malloc(size3);
memcpy(buff4,img.data,sizeof(img.data));
char OOBR_stack = buff3[size3+100];
char OOBR_heap = buff4[100];
buff3[size3+100]='c';
buff4[100]='c';
if(size3>10){
buff4=0;
}
else{
free(buff4);
}
free(buff2);
}
fclose(fp);
}
int main(int argc,char **argv)
{
ProcessImage(argv[1]);
}
检测到漏洞:
1.未检查的返回值:未检查 fopen() 函数的返回值,这可能导致空指针取消引用。
2.越界读取:memcpy()函数用于将数据从img.data复制到buff1和buff2,而不检查目标缓冲区的大小,这可能导致越界读取。
3.越界写入:buff3 和 buff4 数组的写入没有检查源缓冲区的大小,这可能导致越界写入。
fopen()
在行中检查了的值if(fp == NULL)
。此外,漏洞 4 的措辞具有误导性——它引用的代码行中存在两个未初始化的内存访问错误是正确的,但它指向了错误的变量名。最后一个Java 示例是我最喜欢的,因为不幸的是,当我第一次阅读这段代码时,我没有发现它有任何问题。对我来说,它看起来完全无害且熟悉——我在野外看到过类似的代码,但从未有过任何第二个想法。
class Utils
{
// Function to serialize an object and write it to a file
public static void SerializeToFile(Object obj, String filename)
{
try
{
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
// Serialization of the object to file
System.out.println("Serializing " + obj.toString() + " to " + filename);
out.writeObject(obj);
out.close();
file.close();
}
catch(Exception e)
{
System.out.println("Exception: " + e.toString());
}
}
// Function to deserialize an object from a file
public static Object DeserializeFromFile(String filename)
{
Object obj = new Object();
try
{
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
// Deserialization of the object to file
System.out.println("Deserializing from " + filename);
obj = in.readObject();
in.close();
file.close();
}
catch(Exception e)
{
System.out.println("Exception: " + e.toString());
}
return obj;
}
检测到的漏洞:
var express = require('express');
var cookieParser = require('cookie-parser');
var escape = require('escape-html');
var serialize = require('node-serialize');
var app = express();
app.use(cookieParser())
app.get('/', function(req, res) {
if (req.cookies.profile) {
var str = new Buffer(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str);
if (obj.username) {
res.send("Hello " + escape(obj.username)); // <--- GPT-3 makes a mistake here
}
} else {
res.cookie('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5IjoiYmFuZ2Fsb3JlIn0=", {
maxAge: 900000,
httpOnly: true
});
}
res.send("Hello World");
});
app.listen(3000);
检测到的漏洞:
obj.username
逃逸了,但 GPT-3 说它不是。结果:
实验结果表明,GPT-3 能够检测到扫描的 129 个文件中的 85 个文件中的安全漏洞。能力不亚于一个代码审计专家!
{name:'.php', value:50}, {name:'.js', value:20}, {name:'.cs', value:16}, {name:'.c', value:14}, {name:'.java', value:9}, {name:'.py', value:8}, {name:'.rb', value:5}, {name:'.asp', value:3}, {name:'.ts', value:2}, {name:'.go', value:1}, {name:'.html', value:1}
总结
本文介绍了使用ChatGPT审计代码的过程和结果。作者使用了GPT-3检测到了213个安全漏洞,其中85个文件被正确检测到。GPT-3的误报率相对较低,但仍有一些漏洞被错报或漏报。作者认为,GPT-3可以作为一种辅助工具来帮助发现安全漏洞,但不能完全取代人工审计。
GTP-4能处理长代码,就意味着不用将代码进行切割,不用过多引导。在代码审计过程中,展现出了较强的安全意识和分析能力,并通过动态调试和模拟执行更深入地理解代码逻辑。虽然比GPT-3强大许多,但还是有很长的路需要走。
本文作者:公众号:安全女巫
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/198731.html