json.loads()는 임의의 코드 실행에 취약합니까?
이json.loads
Python의 표준에서json
임의의 코드 실행 또는 기타 보안 문제에 취약한 모듈
응용 프로그램이 신뢰할 수 없는 소스로부터 JSON 메시지를 수신할 수 있습니다.
아래 답변은 Windows 10 64비트용 기본 Python 3.4 설치와 관련이 있습니다.또한 이 답변은 py 스캐너에만 적용되며 c 스캐너에는 적용되지 않습니다.
소스 파일은 https://hg.python.org/cpython/file/tip/Lib/json을 참조하거나 로컬 python 설치에서 찾을 수 있습니다.
조사.
이 조사와 함께, 이 투고 하단의 레퍼런스 실장을 참조해 주세요.
에 의해 호출된 해석 함수json.loads(s)
에 정의되어 있습니다.\Lib\json\scanner.py
:
parse_object = context.parse_object
parse_array = context.parse_array
parse_string = context.parse_string
parse_float = context.parse_float
parse_int = context.parse_int
parse_constant = context.parse_constant
와 함께context
의 예로서JSONDecoder
정의된 클래스\Lib\json\decoder.py
는 다음 파서를 사용합니다.
self.parse_float = parse_float or float
self.parse_int = parse_int or int
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
self.parse_string = scanstring
self.parse_object = JSONObject
self.parse_array = JSONArray
여기서 각 파서를 보고 임의의 코드 실행에 영향을 받기 쉬운지 여부를 판단할 수 있습니다.
parse_parse_parse
기본값이 사용됩니다.float
기능도 안전합니다.
해석_int
기본값이 사용됩니다.int
기능도 안전합니다.
parse_parse_parse
_CONSTANTS
는 다음 파일과 같은 파일 내에서 정의됩니다.
_CONSTANTS = {
'-Infinity': NegInf,
'Infinity': PosInf,
'NaN': NaN,
}
간단한 조회가 이루어지고 있기 때문에 안전합니다.
parse_string, JSONObject, JSONArray
이 투고 마지막에 실장되어 있는 것을 보면 알 수 있듯이, 실행할 수 있는 외부 코드는 다음과 같습니다.
부터JSONObject
:
object_pairs_hook
object_hook
부터JSONArray
:
scan_once
object_pairs_hook
,object_hook
디폴트 object_pairs_hook
그리고.object_hook
로 정의된다.None
디코더 이니셜라이저:
def __init__(self, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, strict=True,
object_pairs_hook=None)
scan_once
scan_once
는 다음과 같이 정의됩니다.
self.scan_once = scanner.make_scanner(self)
에서 찾을 수 있는 소스\Lib\json\scanner.py
여기서 알 수 있습니다.scan_once
는 단순히 JSON 오브젝트의 각 부분에 대해 적절한 파서를 호출합니다.
결론
위와 참조 구현으로부터 JSON 디코더가 사용하는 스캐너가 디폴트인 한 임의의 코드가 실행되지 않는 것을 알 수 있습니다.아마도 이 디코더의 사용을 통해 커스텀 디코더를 사용함으로써 가능합니다.__init__
대신 임의 코드를 실행하도록 하는 매개 변수입니다만, 저는 그렇게 생각하지 않습니다.
실행
백슬래시
BACKSLASH = {
'"': '"', '\\': '\\', '/': '/',
'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t',
}
스트링 챈크
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
스캔 문자열
def py_scanstring(s, end, strict=True,
_b=BACKSLASH, _m=STRINGCHUNK.match):
"""Scan the string s for a JSON string. End is the index of the
character in s after the quote that started the JSON string.
Unescapes all valid JSON string escape sequences and raises ValueError
on attempt to decode an invalid string. If strict is False then literal
control characters are allowed in the string.
Returns a tuple of the decoded string and the index of the character in s
after the end quote."""
chunks = []
_append = chunks.append
begin = end - 1
while 1:
chunk = _m(s, end)
if chunk is None:
raise ValueError(
errmsg("Unterminated string starting at", s, begin))
end = chunk.end()
content, terminator = chunk.groups()
# Content is contains zero or more unescaped string characters
if content:
_append(content)
# Terminator is the end of string, a literal control character,
# or a backslash denoting that an escape sequence follows
if terminator == '"':
break
elif terminator != '\\':
if strict:
#msg = "Invalid control character %r at" % (terminator,)
msg = "Invalid control character {0!r} at".format(terminator)
raise ValueError(errmsg(msg, s, end))
else:
_append(terminator)
continue
try:
esc = s[end]
except IndexError:
raise ValueError(
errmsg("Unterminated string starting at", s, begin))
# If not a unicode escape sequence, must be in the lookup table
if esc != 'u':
try:
char = _b[esc]
except KeyError:
msg = "Invalid \\escape: {0!r}".format(esc)
raise ValueError(errmsg(msg, s, end))
end += 1
else:
uni = _decode_uXXXX(s, end)
end += 5
if 0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u':
uni2 = _decode_uXXXX(s, end + 1)
if 0xdc00 <= uni2 <= 0xdfff:
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
end += 6
char = chr(uni)
_append(char)
return ''.join(chunks), end
scanstring = c_scanstring or py_scanstring
공백
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
공백 공간_STR
WHITESPACE_STR = ' \t\n\r'
JSONObject
def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
s, end = s_and_end
pairs = []
pairs_append = pairs.append
# Backwards compatibility
if memo is None:
memo = {}
memo_get = memo.setdefault
# Use a slice to prevent IndexError from being raised, the following
# check will raise a more specific ValueError if the string is empty
nextchar = s[end:end + 1]
# Normally we expect nextchar == '"'
if nextchar != '"':
if nextchar in _ws:
end = _w(s, end).end()
nextchar = s[end:end + 1]
# Trivial empty object
if nextchar == '}':
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end + 1
pairs = {}
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end + 1
elif nextchar != '"':
raise ValueError(errmsg(
"Expecting property name enclosed in double quotes", s, end))
end += 1
while True:
key, end = scanstring(s, end, strict)
key = memo_get(key, key)
# To skip some function call overhead we optimize the fast paths where
# the JSON key separator is ": " or just ":".
if s[end:end + 1] != ':':
end = _w(s, end).end()
if s[end:end + 1] != ':':
raise ValueError(errmsg("Expecting ':' delimiter", s, end))
end += 1
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass
try:
value, end = scan_once(s, end)
except StopIteration as err:
raise ValueError(errmsg("Expecting value", s, err.value)) from None
pairs_append((key, value))
try:
nextchar = s[end]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end]
except IndexError:
nextchar = ''
end += 1
if nextchar == '}':
break
elif nextchar != ',':
raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1))
end = _w(s, end).end()
nextchar = s[end:end + 1]
end += 1
if nextchar != '"':
raise ValueError(errmsg(
"Expecting property name enclosed in double quotes", s, end - 1))
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end
pairs = dict(pairs)
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end
JSONARay
def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
s, end = s_and_end
values = []
nextchar = s[end:end + 1]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end:end + 1]
# Look-ahead for trivial empty array
if nextchar == ']':
return values, end + 1
_append = values.append
while True:
try:
value, end = scan_once(s, end)
except StopIteration as err:
raise ValueError(errmsg("Expecting value", s, err.value)) from None
_append(value)
nextchar = s[end:end + 1]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end:end + 1]
end += 1
if nextchar == ']':
break
elif nextchar != ',':
raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1))
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass
return values, end
scanner.make_scanner
def py_make_scanner(context):
parse_object = context.parse_object
parse_array = context.parse_array
parse_string = context.parse_string
match_number = NUMBER_RE.match
strict = context.strict
parse_float = context.parse_float
parse_int = context.parse_int
parse_constant = context.parse_constant
object_hook = context.object_hook
object_pairs_hook = context.object_pairs_hook
memo = context.memo
def _scan_once(string, idx):
try:
nextchar = string[idx]
except IndexError:
raise StopIteration(idx)
if nextchar == '"':
return parse_string(string, idx + 1, strict)
elif nextchar == '{':
return parse_object((string, idx + 1), strict,
_scan_once, object_hook, object_pairs_hook, memo)
elif nextchar == '[':
return parse_array((string, idx + 1), _scan_once)
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
return None, idx + 4
elif nextchar == 't' and string[idx:idx + 4] == 'true':
return True, idx + 4
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
return False, idx + 5
m = match_number(string, idx)
if m is not None:
integer, frac, exp = m.groups()
if frac or exp:
res = parse_float(integer + (frac or '') + (exp or ''))
else:
res = parse_int(integer)
return res, m.end()
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
return parse_constant('NaN'), idx + 3
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
return parse_constant('Infinity'), idx + 8
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
return parse_constant('-Infinity'), idx + 9
else:
raise StopIteration(idx)
def scan_once(string, idx):
try:
return _scan_once(string, idx)
finally:
memo.clear()
return _scan_once
make_scanner = c_make_scanner or py_make_scanner
언급URL : https://stackoverflow.com/questions/38813298/is-json-loads-vulnerable-to-arbitrary-code-execution
'programing' 카테고리의 다른 글
Python에서 JSON을 SQLite로 변환 - json 키를 데이터베이스 열에 올바르게 매핑하는 방법 (0) | 2023.03.17 |
---|---|
Angular와 함께 사용하기 좋은 백엔드는 무엇입니까?JS (0) | 2023.03.17 |
"NoClassDefFoundError: 클래스를 초기화할 수 없습니다" 오류 (0) | 2023.03.17 |
SQL Server의 IsNull() 함수와 동등한 Oracle은 무엇입니까? (0) | 2023.03.17 |
인터페이스 {}에서 []바이트 변환(골랑) (0) | 2023.03.17 |