Ахова межаў для AI-агентаў: раскадаванне схаваных IP і даменаў да запуску
Ахова межаў для AI-агентаў: раскадаваць укладзеныя base64, hex і URL-кадаванне, нармалізаваць дзіўныя фарматы IP і фільтраваць імёны файлаў, каб чужыя мэты ніколі не запускаліся.
У звычных праектах агентаў агент спакойна вважаецца надзеяным элементам. Ён выкананае код у «піскулярнай касце», якая належыць командзе, рэдагуе файлы, якія яму даўця, і чытае лог-файлы, якія заносіць каманда. Аднак існуе сітуацыя, якая парадуруе гэтыя дазволы: агент працуе з тэкстам, напісаным іншым чалавекам, і частка гэтага тэксту прызначана для таго, каб прымусіць яго выконваць задачы, якія не ў межах яго заведама.
Сцэнарый
Разглядзімо рэкурзіўную мадэль мовы, якая была найнята для апраўданага тэста на прахраўленне. У ўказаннях да яе прыводзяцца дыяпазоны IP-адрэсаў і домены, да якіх яна можа даступіцца, а ўсё, што не ў гэтым спіску, забараняецца, незалежна ад таго, якім шляхам мадэль праглытае да яго доступ. У певны момент мадэль выдае команду для шэлу. Паміж гэтым выходам і самым шэлам павінен быць контрольны пункт, які сам парадуруе гэтыя межы, а не спакойваецца на тым, што мадэль яе запамятае.
Порэшчанне каманды з чорнай/белай лістай стракаў недастаткова. Нападчыкі, а таксама модэлі, якія падаюць пад вплыв прачытанага тэксту, рэдка калі пішуць забраны адрэса адкрыта. Яны яго закрыюць, таму пункт контролю павінен быць такі ж адзінакавы ў выяўленні, як і яны ў закрыцці.
Закрытые ў кодаванні
Падазроўваючы, што ўсё, што дазволена, — гэта target.htb, наўмовны пераглед каманды шукае сабстракты, якія нагадваюць домены, і порэшчвае іх з гэтым значэннем. Гэта працуе, пакуль каманда не будзе выглядаць так:
curl http://localhost/exec?cmd=ping%20192.168.2.1
192.168.2.1 не практыкуецца у чыстам тэксце, таму што ён кодаваны ў відсотках унутрь URL. Дэкодаванне адной раз і ён будзе там. Калі яго загорнуць у base64, разбіць на часты з апаведамленнямі hex, або створыць кальцы разных роўней, то пераказ, які чытае толькі буквальны тэкст, працавае ў всіх варыантах, хоця кожны з іх вярнуецца да таго ж хоста, які не належыць да дапусканага дыячага прастору.
Гэта не ўскладненне адной лягкодачы. Значэнне зазвычай можа быць запісанае багато аднаковымі способамі, і верыфікатор, які аналізуе поверхневы строк, пераказвае толькі адны з іх.
Рашэннем ёсць спачатку дэкодаванне, а потым ацэнка. Перш чым пераказ пераказвае, чы рэгістрарыцца адрэса, ён рэкурсыўна знімае кожную лягкодачу, якую можа знайсці, і ацэнівае чыстыя значэнні, якія застаюцца:
def _de_cloak_payloads(cmd: str, depth: int = 0, max_depth: int = 3) -> set[str]:
"""
Recursively searches for base64, hex, and URL encodings inside cmd.
Returns a set of all extracted/decoded plain strings.
"""
extracted = {cmd}
if depth >= max_depth:
return extracted
# 1. URL Decoding
decoded_url = urllib.parse.unquote(cmd)
if decoded_url != cmd:
extracted.update(_de_cloak_payloads(decoded_url, depth + 1, max_depth))
# 2. Hex escape and raw hex string decoding
for match in re.findall(r"(?:\\x[0-9a-fA-F]{2})+", cmd):
hex_bytes = bytes.fromhex(match.replace("\\x", ""))
extracted.update(_de_cloak_payloads(hex_bytes.decode("utf-8", errors="ignore"), depth + 1, max_depth))
# 3. Base64 decoding
for match in re.findall(r"\b[A-Za-z0-9+/]{12,}={0,2}\b", cmd):
padded = match + "=" * ((4 - len(match) % 4) % 4)
decoded = base64.b64decode(padded.encode("ascii")).decode("utf-8", errors="ignore")
extracted.update(_de_cloak_payloads(decoded, depth + 1, max_depth))
return extracted
Рэкурсія практыкуе выходныя даны кожной спробы дэкадавання знову ў тую ж функцыю, пры чым існуе меры для глыбіні. Уявіце кодаванне URL, якое знаходзіцца ўнутры кодавання base64, якое знаходзіцца ўнутры эксклюзіўных знакаў hex: гэта працэс расплетаецца пашагоўна, і кожны строк, які утварыўся на паўтарных этапах, а не толькі самы внутрэшні, дадаецца да колекцыі, якая пазней аналізуецца.
У выніку выходзіць калекцыя: первоначальны запуск плюс кожная простая значэнне, якое можна вярнуць праз дэкадавання, на глыбіні да трохоў слоёў. Кожна з іх пераглядаецца за межамі дазволенага дыячага поля. Калі хоць адна з іх містыць адрэсу за межамі дазволенага дыячага поля, весь запуск адхіляецца, нават калі буквальны тэкст модэлю ніколі не згадваў тую адрэсу.
Скрытыя ў фарматах чыслаў
Кодаванне — не ўсё, што можа служыць маскаванню. Адрэса IP — гэта чысло, а у чыслаў існуе кальколька правільных спосабаў запісу. 192.168.2.1 можа выступаць як адны 32-бітны цэлы чыслы, як чатыры октальныя або шысцядзецімковыя часткі, або як сумешаны варыянт з допамогай заповнень, які прыманяе большасць інструментаў. Звычайныя выразы рэгулярных выразаў, налаштаваныя для дробавага дэцымальнага фармата, не врачаюць нічога з гэтага, пры тым як рэальныя сетавыя стакі ператвараюць іх у адну і тую ж машыну.
def _normalize_ip_token(token: str) -> str | None:
token = token.strip().lower()
# A bare integer or hex integer standing in for a full IP
if token.isdigit() or (token.startswith("0x") and all(c in "0123456789abcdef" for c in token[2:])):
val = int(token, 16) if token.startswith("0x") else int(token)
if 0 <= val <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:
if val > 1024 or val in (0,):
return str(ipaddress.ip_address(val))
# Dotted octal or dotted hex, one part at a time
if "." in token:
parts = token.split(".")
if len(parts) == 4:
normalized_parts = []
for p in parts:
val = int(p, 16) if p.startswith("0x") else int(p, 8) if p.startswith("0") and len(p) > 1 else int(p)
if 0 <= val <= 255:
normalized_parts.append(str(val))
if len(normalized_parts) == 4:
return ".".join(normalized_parts)
return None
Кожны аддытывны спэлінг перапісываецца у стандартную знакавую нотацыю, прычыму людзі можаць яго парабяліць з дазволенымі дыяпазонамі. Гэта значыць, што 0xC0A80201 і 192.168.2.1 лічыцца як адны хост, які носіць разныя «адзёбы».
Домены і назвы файлаў
Перакананне IP-адрэсаў мае на меце выявленне маскавання. Перакананне доменаў мае працоўную складнасць наадворот: сетка должна быць дастаткова шырокай, каб зафіксаваць справжнія доменавыя пасыланні, не пазначаючы кожную назву файла, якая мае крапку.
Рэгулярны выраз, створаны для пошуку элементаў у фармаце домена, таксама добра падходзіць для config.yaml або report.pdf, як і для evil.example.com, адтуды што кожны з іх мае аднаковую структуру: літэры, крапка і ўсё тыя ж літэры. Дадатковая дэкодаванне тут не дапамагае. Дапамага другі спіс — а самэт частыя расшырэнні файлаў, якія парабяляюцца з тым, што знаходзіцца на месцы верхніявынесенага домена:
tld = token.rsplit(".", 1)[-1]
if tld in _FILE_EXTENSIONS:
continue
if not any(token == d or token.endswith("." + d) for d in self.domains):
raise ScopeViolation(f"Domain {token!r} is outside engagement scope.")
Спісак трэба налаштаваць, калі ўжо тое, калі іншае ўскладнюе працу. Калі ён занадта короткі, звычныя шляхі да файлаў продовжваюць выклікваць алармы, таму корыстувальнікі научаюцца абоўсумова ігнораваць гэты інструмент. Калі ж ён занадта дзьвіг, справжні домены, якія не належаць да дыялянку, можу сховацца за суфіксам, які якраз не ўключаны. Рэальны спісак включае калькі дзесятка расшырэнняў — py і sh аж да pem і elf, калі ўжо тое, што справы часта стосуюцца розных тыпаў файлаў, і кожны з іх можа вызваць хыбны аларм.
Полны адзінчы перагляд праходзіць кожны расшыфраваны кандыдат праз оба фільтры, прычым да таго, калі дазволяе запускіць каманду:
def check(self, cmd: str) -> None:
payloads = _de_cloak_payloads(cmd)
for payload in payloads:
if self.networks:
for m in _IPV4_RE.finditer(payload):
self._validate_ip(m.group(1))
for token in re.split(r"[\s\"'$,;()|&<>`\\/]", payload):
normalized = _normalize_ip_token(token)
if normalized:
self._validate_ip(normalized)
if self.domains:
for m in _DOMAIN_RE.finditer(payload):
token = m.group(0).lower()
tld = token.rsplit(".", 1)[-1]
if tld in _FILE_EXTENSIONS:
continue
if not any(token == d or token.endswith("." + d) for d in self.domains):
raise ScopeViolation(f"Domain {token!r} is outside engagement scope.")
Адзін некарэчны значэнне ў будзь-ям расшыфраваным варыянцы зупіняе команду. Не трэба падтверджваць, што мела на увазе мадэль; дастатнька тое, каб адна правдападобная інтэрпрэтацыя зместу команды дася да забранага адрэса.
За межамі тэставання безпекі
Як толькі адзін раз з’яўляецца кантэкст тэставання на пронікненне, застаецца шаблон, які ўзаемна ўжываецца кожным агентам, які працуе з ненадзеяным уводам. Уявіце працэс затверджэння, калі сума паўставляецца да ліміту выдаткаў, фільтр, які паўставляе наданы тэкст у адносе да спіску забороненых значэнняў, або пераказ доступу, які пераглядае запрошаны рэсурс у адносе да спіску дазволеных. Неадкультурная рэалізацыя тэстуе будзь-яю буквальную строчку, якая прыйшла; надзеяная ж спачатку канонізуе кожную эквівалентную форму значэння і тады тэстуе рэзультат. Механізм захісту на адэнтных командах дае урок, які таксама прыменяецца да полей JSON, шляхоў да файлаў і параметраў URL, калі агент вырашае, чы робіць далей.
Цена — гэта наступны вопыт
Усё, што было сказана вышэй, стосуецца правільнасці і контролю: чы робат застаецца ў межах заданых ліній, чы файл пасля патча все ўсё працюе, і чы можна безпечна ацэніць дэкодаваныя значэнні. Нічога з гэтага не адпавядае на болей просты вопыт, які з’яўляецца як толькі з’являюцца рэальныя корыстувальнікі. Рэкурэнтныя сістэмы выконваюць многа вызоваў модэляў, а кожны такі вызов коштае часу і грошаў. Тое, звядзе камплекція гэтых расходаў, і што на самай працы яго зменшыць, — гэта аднае, а ўсё іншае — аднае.
Практычныя рэкамендацыі для тых, хто стварае падобныя механізмы: трэба спрыятаць дэкодэр і нормалізатор як частку мер захавання, тэставаць іх з агрэсіўнымі данымі, такімі як тройная інкодавання і адрэсы з разным базамі, і рэгулярна пераглядаць спіс дазволеных расшырэнняў. Захід, які быў правільны ў пярэднім квартале, можа стаць неэфектывным, калі у працэс работы з’являюцца новыя тыпы файлаў і інструменты.
Спаднія матэрыялы
- Сістэмы з мнагае агентамі — гэта сістэмы-бэкэнд з нэдэтэрміністычнымі элементамі — Чаму сістэмы з мнагае агентамі, якія вікорыстоўваюцца ў прымэтнах, спрыяюць стварэнню сістэм-бэкэнд з такімі элементамі: чысткія кантракты, запыткі як параметры налаштавання, строгі кантэкст, адна роля на агента, надзеяны код на адпаведнае функцыонаванне.
- Практычныя прымэты: стварэнне автонамнага агента-крытыка для інтэрфейсу з новым Google — Паследовны апісанне практычных прымэтов па стварэнню автонамнага агента-крытыка для інтэрфейсу з новым Google: кантракты, перакананні ў правільнасці, а таксама шаблоны коду, якія можна прыменіць командам, якія вікорыстоўваюць гэты патэрн.