«Меня удручает ваш уровень кибербезопасности»

Краткая сводка: GPT-3 обнаружила 213 уязвимостей безопасности в git-репозитории. Для сравнения: один из лучших коммерческих инструментов на рынке (разработанный респектабельной компанией, которая занимается кибербезопасностью) нашёл лишь 99 проблем; впрочем, этот инструмент предоставляет контекст в более структурированном формате. После ручной проверки случайной выборки 50 из 213 уязвимостей, выявленных GPT-3, только одна оказалась ложноположительной. У обоих инструментов было много ложноотрицательных результатов.

Введение


За последние годы сфера искусственного интеллекта и машинного обучения испытывала бешеный рост, открыв новое пространство возможностей. Одной из таких возможностей, привлёкших внимание, стал анализ кода на основе ИИ, а конкретно использование ИИ-моделей для выявления уязвимостей безопасности в коде. В этом эксперименте мы использовали GPT-3 компании OpenAI для поиска уязвимостей безопасности в репозитории кода, содержащем 129 файлов с уязвимостями.

Как это работает


Использованный мной вариант GPT-3 (text-davinci-003) имеет окно контекста в 4000 токенов, что приблизительно равно трём тысячам английских слов. Это значит, что за один запрос он может обрабатывать не больше нескольких сотен строк кода. К сожалению, современная архитектура GPT-3 не может справиться с целым репозиторием за раз.

Чтобы обойти эту проблему, мне пришлось по отдельности сканировать каждый файл при помощи GPT-3. Значит, у GPT-3 могли возникнуть трудности с поиском уязвимостей безопасности, возникающих при взаимодействии нескольких файлов кода; впрочем, иногда импорт/экспорты оказывались достаточно понятны, чтобы без изучения кода сделать предположение о том, что выполняют эти функции. Такое бывало довольно часто, особенно когда исходный код использовал популярные библиотеки наподобие express.js, Flask, стандартной библиотеки Python, стандартной библиотеки C и так далее. Вероятно, GPT-3 частично или полностью запомнил самые популярные библиотеки (или закодировал их каким-то иным способом). В случае кода, анализируемого в этой статье, у GPT-3 имелось достаточно знаний об импортированных библиотеках, чтобы точно выявлять уязвимости безопасности без необходимости изучения кода импортированных библиотек.

Чтобы быть справедливым к GPT-3, я скажу, что многие коммерческие сканеры уязвимостей при выполнение статического анализа на самом деле вряд ли изучают код импортированных библиотек, поэтому работа ИИ не сильно отличается от того, как действует большинство инструментов на рынке.

Анализируемый код


Каждая папка в репозитории имеет имя, соответствующее типу уязвимости безопасности, и содержит файлы с примером кода, включающего одну или несколько уязвимостей. В некоторых файлах находится тривиальный код, однако во многих содержатся довольно реалистичные фрагменты кода, с которыми вы можете столкнуться в кодовой базе продакшена (примечание: но это всё равно фрагменты и в них отсутствует контекст более крупной кодовой базы). Файл README.md в каждой папке содержит анализ уязвимостей безопасности, проведённый GPT-3 для всех файлов в этой папке.

Давайте рассмотрим несколько примеров, чтобы узнать, как же поработал GPT-3!

Пример 1 (тривиальный)


Это простая программа на C, уязвимая к Format String Attack:

#include <stdio.h>
 
int main(int argc, char **argv) {
    printf(argv[1]);
 
    return 0;
}

А вот анализ GPT-3 (совершенно верный):

Vulnerabilities Detected:
1. Unvalidated user input: The program does not check the length of the user input, which could lead to a buffer overflow attack.
2. Format string vulnerability: The program does not check the format of the user input, which could lead to a format string attack.

Перевод:

Выявленные уязвимости:
1. Невалидируемый пользовательский ввод: программа не проверяет длину пользовательского ввода, что может привести к атаке переполнения буфера
2. Уязвимость форматирования строк: программа не проверяет формат пользовательского ввода, что может привести к атаке форматирования строк

Пример 2 (менее тривиальный)


Давайте проверим ИИ с менее тривиальной программой на высокоуровневом языке, например, с этой программой на C#, содержащей уязвимость Log Forging. Лично я видел схожие (хоть и менее тривиальные) примеры кода в опенсорсных проектах и кодовых базах продакшена:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebFox.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class LogInjection : ControllerBase
    {
        private readonly ILogger<LogInjection> _logger;


        public LogInjection(ILogger<LogInjection> logger)
        {
            _logger = logger;
        }

        [HttpGet("{userInfo}")]
        public void injectLog(string userInfo)
        {
            _logger.LogError("error!! " + userInfo);
        }
    }
}

А вот вывод GPT-3:

Vulnerabilities Detected:
1. Log injection vulnerability: The code is vulnerable to log injection attacks as user input is being directly logged without any sanitization.

Перевод:

Выявленные уязвимости:
1. Уязвимость Log injection: код уязвим к атакам log injection, поскольку логирование пользовательского ввода выполняется напрямую и без санации.

Пример 3 (нетривиальный)


Приведённая ниже программа на C считывает и обрабатывает изображение. Она содержит множество уязвимостей безопасности, в частности Out Of Bounds read и write:

#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]);
}

Вывод GPT-3:

Vulnerabilities Detected:
1. Unchecked return value: The return value of the fopen() function is not checked, which could lead to a null pointer dereference.
2. Out-of-bounds read: The memcpy() function is used to copy data from img.data to buff1 and buff2 without checking the size of the destination buffer, which could lead to an out-of-bounds read.
3. Out-of-bounds write: The buff3 and buff4 arrays are written to without checking the size of the source buffer, which could lead to an out-of-bounds write.
4. Uninitialized memory access: The OOBR_stack and OOBR_heap variables are accessed without being initialized, which could lead to undefined behavior.
5. Memory leak: The buff4 array is not freed if the size3 variable is greater than 10, which could lead to a memory leak.

Перевод:

Выявленные уязвимости:
1. Непроверяемое возвращаемое значение: возвращаемое функцией fopen() значение не проверяется, что может привести к разыменованию нулевого указателя.
2. Out-of-bounds read: функция memcpy() используется для копирования данных из img.data в buff1 и buff2 без проверки размера целевого буфера, что может привести к out-of-bounds read.
3. Out-of-bounds write: запись в массивы buff3 и buff4 выполняется без проверки размера исходного буфера, что может привести к out-of-bounds write.
4. Доступ к неинициализированной памяти: доступ к переменным OOBR_stack и OOBR_heap выполняется без инициализации, что может привести к неопределённому поведению.
5. Утечка памяти: массив buff4 не освобождается, если переменная size3 больше 10, что может привести к утечке памяти.

GPT-3 снова прав!

Пример 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;
    }
}

Вывод GPT-3:

Vulnerabilities Detected:
1. Insecure Deserialization: Deserializing untrusted data can lead to remote code execution.
2. Insecure File Permissions: The file being written to or read from may have insecure permissions, allowing unauthorized access.

Перевод:

Выявленные уязвимости:
1. Небезопасная десериализация: десериализация непроверенных данных может привести к удалённому исполнению кода.
2. Небезопасные разрешения файлов: файл, в который выполняется запись или из которого выполняется считывание, может иметь небезопасные разрешения, что обеспечивает возможность неавторизованного доступа.

GPT-3 снова прав.

Важный «сдвиг влево» в кибербезопасности


Сдвиг влево — это процесс применения мер безопасности на более ранних этапах процесса разработки, например, тестирование на баги безопасности до развёртывания кода в продакшене.

ИИ-анализ кода может стать важным сдвигом влево в кибербезопасности благодаря применению наилучших мер безопасности и обучению разработчиков кибербезопасности в процессе написания и коммитов кода. Это может взрастить культуру надёжной кибербезопасности в организации и даже предотвратить следующий крупный хак, но…

Но технология пока неидеальна и в ней отсутствуют многие аспекты, которые не пропустил бы при аудите опытный профессионал в кибербезопасности. Тем не менее, для большой языковой модели общего назначения ИИ справляется удивительно хорошо; к тому же он гораздо дешевле своих альтернатив.

О дате выпуска GPT-4 пока не заявлено, но я уверен, что эти большие языковые модели продолжат развиваться, получая всё больше и больше возможностей.

Ложноположительные срабатывания


После ручного анализа примерно 50 из 213 уязвимостей, выявленных GPT-3, я нашёл единственную ложноположительную в строке 13 этой программы на Javascript:

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);

Вывод GPT-3:

Vulnerabilities Detected:
— Insecure cookie settings (no secure flag set)
— Unvalidated deserialization of user input (cookie)
— Potential XSS vulnerability due to lack of escaping of user input (username)

Перевод:

Выявленные уязвимости:
— Небезопасные параметры куки (не установлен флаг secure)
— Невалидированная сериализация пользовательского ввода (cookie)
— Потенциальная уязвимость XSS из-за отсутствия экранирования пользовательского ввода (username)

GPT-3 прав насчёт первых двух уязвимостей, однако третья уязвимость ложноположительная — obj.username экранирован, хотя GPT-3 утверждает, что это не так.

Результаты


Результаты эксперимента показали, что GPT-3 способен выявить уязвимости безопасности в 85 из 129 просканированных файлов. Впечатляет!

Скрипт summarize_results.py генерирует полную сводку результатов GPT-3:

Vulnerabilities detected in 86 / 129 files.
Detected 213 vulnerabilities in total.

Frequency of introductory sentences used in GPT-3's responses (1 response / file scanned):
{'vulnerabilities detected': 73, 'no vulnerabilities detected.': 43, 'vulnerability detected': 6, 'answer': 2, 'potential vulnerabilities detected': 2, 'analysis': 1, 'security vulnerabilities detected': 1, 'no response given': 1}

Distribution of file types scanned:
129 files of code in total (excluding markdown and flatfiles)
{'.php': 50, '.js': 20, '.cs': 16, '.c': 14, '.java': 9, '.py': 8, '.rb': 5, '.asp': 3, '.ts': 2, '.go': 1, '.html': 1}

Перевод:

Уязвимости выявлены в 86 / 129 файлов.
Всего выявлено 213 уязвимостей.

Частота вводных предложений, использованных в ответах GPT-3 (1 ответ / просканированный файл):
{'vulnerabilities detected': 73, 'no vulnerabilities detected.': 43, 'vulnerability detected': 6, 'answer': 2, 'potential vulnerabilities detected': 2, 'analysis': 1, 'security vulnerabilities detected': 1, 'no response given': 1}

Распределение типов просканированных файлов:
Всего 129 файлов с кодом (за исключением markdown и неструктурированных файлов)
{'.php': 50, '.js': 20, '.cs': 16, '.c': 14, '.java': 9, '.py': 8, '.rb': 5, '.asp': 3, '.ts': 2, '.go': 1, '.html': 1}

Сравнение с коммерческими инструментами


Чтобы закончить эксперимент, я сравнил результаты GPT-3 с результатами коммерческого сканера уязвимостей Snyk Code, разработанного Snyk — компанией, которая, по моему мнению, делает превосходные продукты в сфере безопасности. Пропустив этот репозиторий через Snyk Code, я выявил 99 уязвимостей безопасности по сравнению с 213, найденными GPT-3.


Одним из ограничивающих факторов стало то, что Snyk Code поддерживает не все языки программирования репозитория, поэтому смог просканировать только 103 файлов, а не 129, как GPT-3.

В заключение


Фрагменты уязвимого кода в этом репозитории взяты с потрясающего ресурса snoopysecurity/Vulnerable-Code-Snippets. Я постарался удалить все встроенные в код комментарии, сообщавшие о том, какие уязвимости безопасности содержатся в конкретных фрагментах. Для этого потребовалось удалить комментарии, содержащие ссылки на посты и статьи, откуда были взяты эти примеры фрагментов. Все ссылки на источники, присутствующие в исходном репозитории, можно найти в файле attributions.md.

Комментарии (19)


  1. Suvitruf
    00.00.0000 00:00
    +2

    А обычные статические анализаторы такое не ловят разве? PVS-Studio, можете сравнить со своим решением? )


    1. Andrey2008
      00.00.0000 00:00
      +2

      У нас недавно заметка была: Хорошо ли ChatGPT ищет ошибки в коде? А по поводу этой статьи, написал ниже.


  1. wolfy_str
    00.00.0000 00:00
    +3

    Есть ещё SonarLint, есть PVS-Studio


  1. viordash
    00.00.0000 00:00
    +8

        fp = fopen(filename,"r"); 
    
        if(fp == NULL)
        {
            printf("\nCan't open file or file doesn't exist.");
            exit(0);
        }
    1. Unchecked return value: The return value of the fopen() function is not checked, which could lead to a null pointer dereference.

    хм, есть же проверка


    1. agray
      00.00.0000 00:00
      +8

      ChatGPT: Ой, извините, я ошиблась. Проверка действительно есть.


    1. Andrey2008
      00.00.0000 00:00
      +5

      Если замечать такие ляпы, то не получится написать восторженную статью :) Ну или автор на самом деле слабый программист и не понимает, что пишет.


  1. vassabi
    00.00.0000 00:00

    хмм .. интересно, а вот по коду страниц на фронте - он сможет уязвимости в беке найти или нет?


  1. BoBRoID
    00.00.0000 00:00

    GPT-3 прав насчёт первых двух уязвимостей, однако третья уязвимость ложноположительная — obj.username экранирован, хотя GPT-3 утверждает, что это не так.

    Он заглянул уровнем ниже, в escape, проанализировал код этой функции, и пришёл к выводу что она не экранирует username ????


  1. kemsky
    00.00.0000 00:00

    Пример с Java сомнительный. `ObjectInputStream` - уязвимость по своей природе, Insecure File Permissions - уязвимость с натягом, сильно зависит от контекста. С таким же успехом отсутствие проверки на разрешенный путь filename тоже можно было бы назвать уязвимостью. Сам код кривой, обработка ошибок плохая, потенциально это отказ в обслуживании. Метод десереализации почему-то возвращает new Object() в случае ошибки - вряд ли это ожидаемое поведение. Думаю любой линтер нашел бы это.


  1. Andrey2008
    00.00.0000 00:00
    +17

    Я не разделяю энтузиазм и восторг автора статьи. Наши собственные недавние эксперименты с ChatGPT показали куда более скромные и неоднозначные результаты. Публикация на эту тему: Хорошо ли ChatGPT ищет ошибки в коде?

    Мне кажется, ChatGPT очаровал автора и он приписывает ему правильные ответы, даже там где их нет. Этим, возможно, и объясняется только одно замеченное ложное срабатывание. Если не хотеть их замечать, то их и не будет :)

    Почему я столь скептичен? Автор скорее всего приводит самые красивые и сильные примеры работы ChatGPT. Согласитесь, вряд ли он отбирал слабые примеры :). Так вот, даже в этих отобранных примерах удачной работы имеются незамеченные автором ложные срабатывания.

    Возьмём первый пример.

    int main(int argc, char **argv) {
        printf(argv[1]);

    В целом я согласен с вторым пунктом: Format string vulnerability. Хотя и тут можно придраться к формулировке. Собственно проверять необязательно, нужно просто по-другому использовать printf. Объяснение ошибки явно проигрывает документации классических статических анализаторов: V618. Ну да ладно, первое сообщение более мне интересно.

    "Unvalidated user input: The program does not check the length of the user input, which could lead to a buffer overflow attack.". На мой взгляд это ложное срабатывание. Нет проверки количества аргументов (переменной argc). Здесь ошибка: возможен выход за границы массива argv. А GPT-3 начинает философствовать про какие-то переполнения буфера. Можно, конечно, сказать, что это одно и то же... Но это тогда можно просто сказать "у вас тут ошибка". Если это так - повезло. А если нет, то извините :). Когда программисты говорят про переполнение буфера? Когда имеется в виду именно работа с нуль-терминированной строкой, неправильное использование strcat, memcpy и т.п.

    Ладно, возможно, это было неубедительное ложное срабатывание. Хорошо, вот код из 3-его примера:

    fp = fopen(filename,"r"); 
    if(fp == NULL)
    {
      printf("\nCan't open file or file doesn't exist.");
      exit(0);
    }

    Не понимаю, как можно сказать, что это правильное предупреждение: Unchecked return value: The return value of the fopen() function is not checked, which could lead to a null pointer dereference. GPT-3 явно облажался, а автор не захотел это заметить.

    В этом-же третьем примере:

    char OOBR_stack = buff3[size3+100];
    char OOBR_heap = buff4[100];

    Uninitialized memory access: The OOBR_stack and OOBR_heap variables are accessed without being initialized, which could lead to undefined behavior.

    Полная фигня. Вот же инициализация. Эти переменные никак нельзя назвать неинициализированными. Другое дело, что при их инициализации происходит выход за границы массива, но это совсем другая ошибка, про которую GPT-3 ничего не сказал. Ещё GPT-3 неправ, говоря про доступ к неинициализированным переменным OOBR_stack и  OOBR_heap. Они вообще нигде не используются.

    В общем, это предупреждение на первый взгляд выглядит умным и полезным, но по своей сути совершенно мимо. Скорее всего, также дело обстоит и с другими ошибками, которые в статье не приведены.

    P.S. Слишком пафосно называть всё это уязвимостями. Это просто ошибки. Возможно, некоторые из них являются потенциальными уязвимостями, но не более того. Вот когда найденный дефект можно использовать в своих целях, то тогда да - это уязвимость. Иначе это просто баг, которых тысячи в любых приложениях :). Я то точно знаю, что таких багов полно везде. С помощью PVS-Studio мы уже обнаружили более 15000 багов в открытых проектах. Но мы скромнее и не называем это УЯЗВИМОСТЯМИ!! ААА! Страшно, бойтесь! :)

     


    1. Groramar
      00.00.0000 00:00
      +1

      К слову интересно было б 15000 ваших найденных багов сеточке скормить :)

      Думаю что это кто-то рано или поздно сделает.


    1. domix32
      00.00.0000 00:00

      3.4.1 Exploitation - similar to common buffer overflows
      Format string vulnerabilities sometimes offer a way around buffer length
      limitations and allow exploitation that is similar to common buffer overflows

      Есть атаки "похожие" на переполнение.


      1. Andrey2008
        00.00.0000 00:00

        Не понял мысль и как это связано с моим комментарием. Прошу пояснить.


        1. domix32
          00.00.0000 00:00

          Это про то что нытьё GPT про переполнение буфера есть ложноположительный результат.

           which could lead to a buffer overflow attack


  1. ufm
    00.00.0000 00:00
    +8

    Как можно доверять какую-то проверку правильности чего угодно программе, которая на вопрос "сколько букв в слове шесть" отвесчает 'В слове "шесть" 6 букв.' - для меня выше понимания.


  1. dmitry_lyzin
    00.00.0000 00:00
    +2

    В Примере 3 глаз сразу зацепился за:

            char* buff1=(char*)malloc(size1);
    
            memcpy(buff1,img.data,sizeof(img.data));
            free(buff1);

    явный баг - буфер выделили, туда чего-то записали и тут же его прихлопнули. Интересно, думаю, глянуть поиском, что там происходить с этим buff1 - и, конечно, тут же нашлась чуть ниже строка :

                    buff1[0]='a';

    Короче - баг багованый. А что нам по этому поводу скажет GPT-3? GPT-3 молчит как партизан!

    Интересно выходит, при обучении GPT-3 просматривает за секунду больше кода, чем я видел за всю жизнь, а явных багов не видит! То ли его как-то не так учат, то ли он сам по себе тупой... не знаю. Нейронов не хватает или организованы они как-то не правильно?


    1. domix32
      00.00.0000 00:00

      Забавно, когда бота пытались спрашивать за логику первого порядка и он в среднем не очень осиливал.


  1. flashmozzg
    00.00.0000 00:00
    +1

    Интересно было бы посмотреть на результаты GPT-4


  1. demimurych
    00.00.0000 00:00

    GPT-3 прав насчёт первых двух уязвимостей, однако третья уязвимость ложноположительная — obj.username экранирован, хотя GPT-3 утверждает, что это не так.

    Вызов сторонней библиотеки, которая должна выполнить функцию єкранирования не является гарантией того, что єта сторонняя библиотека всегда делает єто правильно.

    Точнее можно сказать только увидев код библиотеки.

    Случаи, когда подобный сторонний код работает с ошибками - не редкость.