bandit 를 사용 하여 목표 python 코드 를 안전 함수 스 캔 한 사례 분석
python 오픈 소스 라 이브 러 리 코드 에 대한 보안 검색 에서 저 희 는 라 이브 러 리 에서 사용 하 는 함수 가 코드 의 실행 환경 에 예상 치 못 한 영향 을 미 치 는 지 분석 해 야 할 수도 있 습 니 다.전형 적 인 예 를 들 어 python 의 샌 드 박스 탈출 문 제 는 python 의 제3자 라 이브 러 리 를 통 해 시스템 셸 명령 을 수행 할 수 있 습 니 다.이것 은 python 의 샌 드 박스 보호 범위 안에 있 지 않 습 니 다.python 의 샌 드 박스 탈출 문제 에 대해 여 기 는 전개 되 지 않 습 니 다.이것 은 업계 에 여러 해 동안 어려움 을 겪 었 던 문제 입 니 다.python 정부 에서 도 python 의 샌 드 박스 는 완벽 한 보호 방안 이 없습니다.여 기 는 배경 사례 로 만 사 용 됩 니 다.
# subprocess_Popen.py
import subprocess
import uuid
subprocess.Popen('touch ' + str(uuid.uuid1()) +'.txt', shell = True)
여기 서 보 여 주 는 기능 은 subprocess
함수 라 이브 러 리 를 사용 하여 시스템 shell
을 열 고 touch
명령 을 실행 하면 지정 한 파일 이름 의 파일 을 만 들 수 있 으 며 mkdir
과 유사 하 게 폴 더 를 만 들 수 있 습 니 다.이 파일 이 성공 적 으로 실 행 된 후에 현재 디 렉 터 리 에서 uuid
무 작위 로 명 명 된 txt 파일 을 생 성 할 수 있 습 니 다.
[dechin@dechin-manjaro bandit_test]$ python3 subprocess_Popen.py
[dechin@dechin-manjaro bandit_test]$ ll
4
-rw-r--r-- 1 dechin dechin 0 1 26 23:03 b7aa0fc8-5fe7-11eb-b5d3-058313e110e4.txt
-rw-r--r-- 1 dechin dechin 123 1 26 23:03 subprocess_Popen.py
그러나 이번 관심 사 는 이 함수 와 어떤 기능 을 수행 하 는 것 이 아니 라 이 함수 에 subprocess
이라는 함수 라 이브 러 리 를 사 용 했 습 니 다.python 의 언어 특징 에 따라 시스템 에 이러한 모듈 이 subprocess
라 이브 러 리 를 참조 하면 이 기능 모듈 을 호출 할 수 있 는 모든 함 수 는 subprocess
이 함수 로 호출 할 수 있 습 니 다.다음은 다른 python
입 니 다.
# bad.py
from subprocess_Popen import subprocess as subprocess
subprocess.Popen('touch bad.txt', shell = True)
이 코드 의 목적 은 import subprocess
을 직접 만 들 지 않 는 조건 에서 앞에서 만 든 subprocess_Popen.py
을 통 해 다 리 를 놓 고 subprocess
의 기능 함 수 를 호출 하 는 것 이다.이 스 크 립 트 의 실행 결 과 는 다음 과 같 습 니 다.
[dechin@dechin-manjaro bandit_test]$ python3 bad.py
[dechin@dechin-manjaro bandit_test]$ ll
12
-rw-r--r-- 1 dechin dechin 0 1 26 23:13 0fda7ede-5fe9-11eb-80a8-ad279ab4e0a6.txt
-rw-r--r-- 1 dechin dechin 0 1 26 23:03 b7aa0fc8-5fe7-11eb-b5d3-058313e110e4.txt
-rw-r--r-- 1 dechin dechin 113 1 26 23:13 bad.py
-rw-r--r-- 1 dechin dechin 0 1 26 23:13 bad.txt
drwxr-xr-x 2 dechin dechin 4096 1 26 23:13 __pycache__
-rw-r--r-- 1 dechin dechin 123 1 26 23:03 subprocess_Popen.py
이 결 과 는 우리 가 bad.py
을 성공 적 으로 사용 하여 subprocess_Popen.py
에서 인용 한 subprocess
을 호출 하여 touch
의 파일 을 성공 적 으로 사용 했다 는 것 을 의미한다.여기 서 우리 의 배경 사례 시연 이 끝 났 지만 우 리 는 이러한 사례 에 포 함 된 논 리 를 다시 정리 해 야 한다.우 리 는 원래 자신의 시스템 에 python 의 샌 드 박스 탈출 문 제 를 도입 하지 않 기 를 원 했다.우 리 는 다른 사람 이 전달 한 코드 를 스 캔 할 것 이다.예 를 들 어 다음 에 소개 할
bad.txt
도 구 를 사용 하여 bandit
등'위험 함수'를 차단 할 것 이다.만약 에 저희 가 작성 한 python 라 이브 러 리 나 도 입 된 제3자 python 라 이브 러 리 에 subprocess
과 유사 한 인용 이 존재 한다 면 저희 의 차단 이 효력 을 잃 게 될 것 입 니 다.사용 자 는 임의로 이 인 용 된 다 리 를 통 해 subprocess
의 함수 기능 을 직접 호출 할 수 있 습 니 다.따라서 특수 한 조건 에서 우 리 는 자신의 코드 에 대해 안전 함 수 를 스 캔 하여 다른 사람의 시스템 에 기대 할 수 없 는 안전 위험 을 가 져 오지 않도록 해 야 한다.subprocess
은 그 중의 안전 함수 스 캔 도구 일 뿐 입 니 다.다음은 기본 적 인 설치 와 사용 방법 을 소개 하 겠 습 니 다.pip 로 bandit 설치 하기
이곳 은
bandit
을 직접 사용 하여 pip
을 설치 하고 필요 한 것 이 있 으 면 소스 코드 에서 직접 설치 할 수 있다.bandit
의 사용 에서 국내 미 러 소스 를 설정 하 는 방법 에 대해 서 는 블 로그 에서 python 에 제3자 라 이브 러 리 를 설치 하 는 것 에 대한 소 개 를 참고 할 수 있 습 니 다.
[dechin@dechin-manjaro bandit_test]$ python3 -m pip install bandit
Collecting bandit
Downloading bandit-1.7.0-py3-none-any.whl (115 kB)
|| 115 kB 101 kB/s
Requirement already satisfied: PyYAML>=5.3.1 in /home/dechin/anaconda3/lib/python3.8/site-packages (from bandit) (5.3.1)
Collecting GitPython>=1.0.1
Downloading GitPython-3.1.12-py3-none-any.whl (159 kB)
|| 159 kB 28 kB/s
Requirement already satisfied: six>=1.10.0 in /home/dechin/anaconda3/lib/python3.8/site-packages (from bandit) (1.15.0)
Collecting stevedore>=1.20.0
Downloading stevedore-3.3.0-py3-none-any.whl (49 kB)
|| 49 kB 25 kB/s
Collecting gitdb<5,>=4.0.1
Downloading gitdb-4.0.5-py3-none-any.whl (63 kB)
|| 63 kB 28 kB/s
Collecting pbr!=2.1.0,>=2.0.0
Downloading pbr-5.5.1-py2.py3-none-any.whl (106 kB)
|| 106 kB 26 kB/s
Collecting smmap<4,>=3.0.1
Downloading smmap-3.0.5-py2.py3-none-any.whl (25 kB)
Installing collected packages: smmap, gitdb, GitPython, pbr, stevedore, bandit
Successfully installed GitPython-3.1.12 bandit-1.7.0 gitdb-4.0.5 pbr-5.5.1 smmap-3.0.5 stevedore-3.3.0
설치 가 끝 난 후 다음 명령 을 통 해 설치 성공 여 부 를 검증 할 수 있 습 니 다.
[dechin@dechin-manjaro bandit_test]$ bandit -h
usage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE] [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i] [-f {csv,custom,html,json,screen,txt,xml,yaml}] [--msg-template MSG_TEMPLATE] [-o [OUTPUT_FILE]] [-v] [-d] [-q]
[--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE] [--ini INI_PATH] [--exit-zero] [--version]
[targets [targets ...]]
Bandit - a Python source code security analyzer
positional arguments:
targets source file(s) or directory(s) to be tested
optional arguments:
-h, --help show this help message and exit
-r, --recursive find and process files in subdirectories
-a {file,vuln}, --aggregate {file,vuln}
aggregate output by vulnerability (default) or by filename
-n CONTEXT_LINES, --number CONTEXT_LINES
maximum number of code lines to output for each issue
-c CONFIG_FILE, --configfile CONFIG_FILE
optional config file to use for selecting plugins and overriding defaults
-p PROFILE, --profile PROFILE
profile to use (defaults to executing all tests)
-t TESTS, --tests TESTS
comma-separated list of test IDs to run
-s SKIPS, --skip SKIPS
comma-separated list of test IDs to skip
-l, --level report only issues of a given severity level or higher (-l for LOW, -ll for MEDIUM, -lll for HIGH)
-i, --confidence report only issues of a given confidence level or higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
-f {csv,custom,html,json,screen,txt,xml,yaml}, --format {csv,custom,html,json,screen,txt,xml,yaml}
specify output format
--msg-template MSG_TEMPLATE
specify output message template (only usable with --format custom), see CUSTOM FORMAT section for list of available values
-o [OUTPUT_FILE], --output [OUTPUT_FILE]
write report to filename
-v, --verbose output extra information like excluded and included files
-d, --debug turn on debug mode
-q, --quiet, --silent
only show output in the case of an error
--ignore-nosec do not skip lines with # nosec comments
-x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS
comma-separated list of paths (glob patterns supported) to exclude from scan (note that these are in addition to the excluded paths provided in the config file) (default:
.svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg)
-b BASELINE, --baseline BASELINE
path of a baseline report to compare against (only JSON-formatted files are accepted)
--ini INI_PATH path to a .bandit file that supplies command line arguments
--exit-zero exit with 0, even with results found
--version show program's version number and exit
CUSTOM FORMATTING
-----------------
Available tags:
{abspath}, {relpath}, {line}, {test_id},
{severity}, {msg}, {confidence}, {range}
Example usage:
Default template:
bandit -r examples/ --format custom --msg-template \
"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
Provides same output as:
bandit -r examples/ --format custom
Tags can also be formatted in python string.format() style:
bandit -r examples/ --format custom --msg-template \
"{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
See python documentation for more information about formatting style:
https://docs.python.org/3/library/string.html
The following tests were discovered and loaded:
-----------------------------------------------
B101 assert_used
B102 exec_used
B103 set_bad_file_permissions
B104 hardcoded_bind_all_interfaces
B105 hardcoded_password_string
B106 hardcoded_password_funcarg
B107 hardcoded_password_default
B108 hardcoded_tmp_directory
B110 try_except_pass
B112 try_except_continue
B201 flask_debug_true
B301 pickle
B302 marshal
B303 md5
B304 ciphers
B305 cipher_modes
B306 mktemp_q
B307 eval
B308 mark_safe
B309 httpsconnection
B310 urllib_urlopen
B311 random
B312 telnetlib
B313 xml_bad_cElementTree
B314 xml_bad_ElementTree
B315 xml_bad_expatreader
B316 xml_bad_expatbuilder
B317 xml_bad_sax
B318 xml_bad_minidom
B319 xml_bad_pulldom
B320 xml_bad_etree
B321 ftplib
B323 unverified_context
B324 hashlib_new_insecure_functions
B325 tempnam
B401 import_telnetlib
B402 import_ftplib
B403 import_pickle
B404 import_subprocess
B405 import_xml_etree
B406 import_xml_sax
B407 import_xml_expat
B408 import_xml_minidom
B409 import_xml_pulldom
B410 import_lxml
B411 import_xmlrpclib
B412 import_httpoxy
B413 import_pycrypto
B501 request_with_no_cert_validation
B502 ssl_with_bad_version
B503 ssl_with_bad_defaults
B504 ssl_with_no_version
B505 weak_cryptographic_key
B506 yaml_load
B507 ssh_no_host_key_verification
B601 paramiko_calls
B602 subprocess_popen_with_shell_equals_true
B603 subprocess_without_shell_equals_true
B604 any_other_function_with_shell_equals_true
B605 start_process_with_a_shell
B606 start_process_with_no_shell
B607 start_process_with_partial_path
B608 hardcoded_sql_expressions
B609 linux_commands_wildcard_injection
B610 django_extra_used
B611 django_rawsql_used
B701 jinja2_autoescape_false
B702 use_of_mako_templates
B703 django_mark_safe
이 목록 의 차단 함 수 를 통 해 우 리 는 이른바'위험 함수'가 도대체 어떤 것들 이 있 는 지 알 수 있다.예 를 들 어 자주 사용 하 는 pip
과 subprocess
이 모두 포함 되 어 있다.random
은 셸 에 대한 호출 로'위험 함수'로 분류 되 었 으 며,subprocess
은 가짜 난수 의 성질 때문이다.bandit 상용 사용법
random
파일 을 직접 검색 합 니 다:
[dechin@dechin-manjaro bandit_test]$ bandit subprocess_Popen.py
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.8.5
[node_visitor] INFO Unable to find qualified name for module: subprocess_Popen.py
Run started:2021-01-26 15:31:00.425603
Test results:
>> Issue: [B404:blacklist] Consider possible security implications associated with subprocess module.
Severity: Low Confidence: High
Location: subprocess_Popen.py:3
More Info: https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess
2
3 import subprocess
4 import uuid
--------------------------------------------------
>> Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified, security issue.
Severity: High Confidence: High
Location: subprocess_Popen.py:6
More Info: https://bandit.readthedocs.io/en/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html
5
6 subprocess.Popen('touch ' + str(uuid.uuid1()) +'.txt', shell = True)
--------------------------------------------------
Code scanned:
Total lines of code: 3
Total lines skipped (#nosec): 0
Run metrics:
Total issues (by severity):
Undefined: 0.0
Low: 1.0
Medium: 0.0
High: 1.0
Total issues (by confidence):
Undefined: 0.0
Low: 0.0
Medium: 0.0
High: 2.0
Files skipped (0):
방금 만 든 위험 함수 secrets
을 호출 한 py 파일 py
의 스 캔 을 통 해 우 리 는 그 중의'위험 함수'를 식별 해 냈 습 니 다.이곳 의 subprocess
번 호 는 subprocess_Popen.py
이 고 등급 은 Issue
입 니 다.그러나 만약 에 우리 가 602
으로 다른 함수 가 위험 함수 에 대한 호출 다 리 를 이용 하여 2 차 호출 된 Severity: Low Confidence: High
파일 을 스 캔 하면 우 리 는 다른 결과 라 는 것 을 알 게 된다.
[dechin@dechin-manjaro bandit_test]$ bandit bad.py
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.8.5
[node_visitor] INFO Unable to find qualified name for module: bad.py
Run started:2021-01-26 15:30:47.370468
Test results:
>> Issue: [B404:blacklist] Consider possible security implications associated with subprocess module.
Severity: Low Confidence: High
Location: bad.py:3
More Info: https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess
2
3 from subprocess_Popen import subprocess as subprocess
4
5 subprocess.Popen('touch bad.txt', shell = True)
--------------------------------------------------
>> Issue: [B604:any_other_function_with_shell_equals_true] Function call with shell=True parameter identified, possible security issue.
Severity: Medium Confidence: Low
Location: bad.py:5
More Info: https://bandit.readthedocs.io/en/latest/plugins/b604_any_other_function_with_shell_equals_true.html
4
5 subprocess.Popen('touch bad.txt', shell = True)
--------------------------------------------------
Code scanned:
Total lines of code: 2
Total lines skipped (#nosec): 0
Run metrics:
Total issues (by severity):
Undefined: 0.0
Low: 1.0
Medium: 1.0
High: 0.0
Total issues (by confidence):
Undefined: 0.0
Low: 1.0
Medium: 0.0
High: 1.0
Files skipped (0):
여기 서 실현 되 는 기능 은 위의 예 와 같 지만 이곳 의 bandit
번 호 는 bad.py
이 고 등급 도 Issue
이 되 었 습 니 다.여기 서 관건 은 등급 이 무엇 으로 바 뀌 었 느 냐 가 아니 라 등급 이 바 뀌 었 다.이것 은 604
이 문자열 에 대한 처 리 를 통 해 위험 함 수 를 식별 하기 때문에 이런 2 차 호출 의 특수 장면 에 대해 Severity: Medium Confidence: Low
이 위험 함수 에 대한 호출 을 정확하게 식별 할 수 있 는 것 이 아니 라 2 차 호출 이 발생 할 수도 있다.위험 함수 의 사용 가능성 을 전혀 식별 할 수 없다.2.디 렉 터 리 에 있 는 모든
bandit
파일 을 검색 하고 결 과 를 bandit
파일 에 기록 합 니 다.
[dechin@dechin-manjaro bandit_test]$ bandit *.py -o test_bandit.txt -f txt
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.8.5
[node_visitor] INFO Unable to find qualified name for module: bad.py
[node_visitor] INFO Unable to find qualified name for module: subprocess_Popen.py
[text] INFO Text output written to file: test_bandit.txt
이 사례 는 현재 디 렉 터 리 에 있 는 모든 py 파일 을 스 캔 했 습 니 다.사실은 py
과 txt
두 개 입 니 다.그리고 최종 스 캔 결 과 를 bad.py
파일 에 저장 합 니 다.여기 서 우 리 는 txt 파일 의 구체 적 인 내용 을 보 여주 지 않 습 니 다.아마도 지난 장의 두 실행 결 과 를 통합 시 켰 습 니 다.3.디 렉 터 리 에 있 는 다 중 폴 더 의
subprocess_Popen.py
파일 을 검색 하고 결 과 를 test_bandit.txt
파일 에 기록 합 니 다.만약 우리 가 아래 와 같은 디 렉 터 리 구 조 를 가지 고 있다 면 스 캔 테스트 를 해 야 합 니 다.
[dechin@dechin-manjaro bandit_test]$ tree
.
├── bad.py
├── bad.txt
├── level2
│ └── test_random.py
├── subprocess_Popen.py
├── test_bandit.html
└── test_bandit.txt
1 directory, 6 files
[dechin@dechin-manjaro bandit_test]$ cat level2/test_random.py
# test_bandit.py
import random
a = random.random()
우 리 는 현재 디 렉 터 리 에서 다음 명령 을 실행 할 수 있 습 니 다.
[dechin@dechin-manjaro bandit_test]$ bandit -r . -f html -o test_bandit.html
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.8.5
[html] INFO HTML output written to file: test_bandit.html
여기 서 우리 가 얻 은 결 과 는 py
파일 로 파일 내용 은 다음 그림 과 같다.4.프로필 사용 안 함 부분
html
실행 디 렉 터 리 아래 test_bandit.html
파일 을 만 들 고 다음 설정 을 하면 Issue
에 대한 심 사 를 피 할 수 있 습 니 다.
[bandit]
skips: B404
실 행 된 검색 결 과 는 다음 그림 에서 보 듯 이 .bandit
과 관련 된 Issue 가 목록 에 없습니다.5.
B404
문서 에서 B404
심 사 를 직접 피한다.스 캔 을 기다 리 는 py 파일 의 대응 위험 함수 에 다음 과 같은 설명 을 추가 하면
py
감사 과정 에서 자동 으로 무시 할 수 있 습 니 다.
# bad.py
from subprocess_Popen import subprocess as sb
sb.Popen('touch bad.txt', shell = 1) # nosec
여기 서 우 리 는 최종 회계 감사 결과 에서 bandit
도 이에 따라 보이 지 않 는 것 을 볼 수 있다.아래 그림 과 같다.이 사례 에서 알 수 있 듯 이 bandit
은 안전 방 호 를 위 한 도구 가 아니 라 초보적인 python 코드 안전 함수 사용 규범 적 인 심사 작업 을 하 는 데 사 용 될 뿐 스 캔 된 문제 의 처리 여 부 는 결국 개발 자 자신 에 게 달 려 있다.bandit 단순 성능 테스트
python 언어의 성능 은 매우 제 한 된 것 으로 알려 져 있 기 때문에 bandit 의 성능 도 매우 떨 어 질 수 있 습 니 다.여기 서 bandit 의 성능 이 어떤 수준 인지 정량 적 으로 테스트 해 보 겠 습 니 다.우선 10000 줄 의 py 파일 을 만 듭 니 다.내용 은 모두 위험 함수 로 사 용 됩 니 다.
# gen.py
import os
with open('test_bandit_power.py', 'w') as py_file:
py_file.write('import subprocess as sb
')
for i in range(10000):
py_file.write('sb.Popen(\'whoami\', shell = 1)
')
B604
을 실행 하면 10000 줄 의 위험 함수 파일 bandit
,약 300 KB 의 크기 를 생 성 할 수 있 습 니 다.이때 우 리 는 이 하나의 파일 에 대해 python3 gen.py
스 캔 테스트 를 실시 했다.우 리 는 이 과정 이 매우 길 고 대량의 오류 로 그 를 생 성 한 것 을 발견 했다.
[dechin@dechin-manjaro bandit_test]$ time bandit test_bandit_power.py -f html -o test_power.html
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.8.5
[node_visitor] INFO Unable to find qualified name for module: test_bandit_power.py
[html] INFO HTML output written to file: test_power.html
real 0m6.239s
user 0m6.082s
sys 0m0.150s
10000 줄 의 코드 가 모두 test_bandit_power.py
의 시간 을 필요 로 한다 면 비교적 큰 프로젝트 의 bandit
의 코드 를 스 캔 하 는 시간 은 6s
에 이 를 수 있다.이 시간 은 그리 길지 않 지만 대형 프로젝트 에 있어 서 는 결코 효율 적 인 선택 이 아니다.요약
안전성 에 대한 요구 가 높 은 일부 개발 사업 에 서 는
1000000+
등 위험 함수 사용 이 금 지 될 수 있다.한편,10min
의 역할 은 코드 의 스캐닝 자동 화 를 통 해 안전 위험 함수 에 대한 분석 의견 을 제시 하고 채택 여 부 는 서로 다른 프로젝트 의 관리자 수 요 를 기준 으로 하 는 것 이다.또한 우리 의 테스트 를 통 해 subprocess
은 실제 사용 장면 에서 성능 표현 이 뜻 대로 되 지 않 기 때문에 대형 프로젝트 에서 우 리 는 사용 하 는 것 을 추천 하지 않 고 반드시 사용 해 야 한다 면 맞 춤 형 설정 을 고려 할 수 있다.저작권 성명
본 논문 의 첫 번 째 링크 는 https://www.cnblogs.com/dechinphy/p/bandit.html 이다.
작성 자 ID:DechinPhy
더 많은 원작 문장 참고:https://www.cnblogs.com/dechinphy/
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
파이썬을 사용하여 10진수를 bin, 8진수 및 16진수 형식으로 변환하는 방법은 무엇입니까?텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.