IgnatiusHeo
구현단계 보안약점 제거 기준-XML 삽입 본문
작성일: 230704
※ 본 게시글은 학습 목적으로 행정안전부·KISA의 소프트웨어 보안약점 진단 가이드, 소프트웨어 개발보안 가이드를 참고하여 작성하였습니다.
정리 내용: 소프트웨어 보안약점 진단 가이드(251~263p)
구분 | - 입력데이터 검증 및 표현 |
설계단계 | - XML 조회 및 결과 검증 https://cryptocurrencyclub.tistory.com/90 |
개요 | 검증되지 않은 외부 입력 값이 XQuery 또는 XPath 쿼리문을 생성하는 문자열로 사용되어 공격자가 쿼리문의 구조로 임의로 변경하고 임의의 쿼리를 실행하여 허가되지 않은 데이터를 열람하거나 인증절차를 우회할 수 있는 보안약점이다. |
진단 세부사항 (설계단계) |
① XML문서를 조회하는 기능을 구현하는 경우 XML쿼리 파라미터는 반드시 쿼리를 조작할 수 없도록 필터링해서 사용하거나, 미리 작성된 질의문에 입력값을 자료형에 따라 바인딩해서 사용해야 한다. 또한 XML 조회를 위한 질의문 생성 시 사용되는 입력값과 조회결과에 대한 검증방법(필터링 등)을 설계하고 유효하지 않은 값에 대한 처리방법이 명시되어 있는지 확인한다. ㅇ XML 조회에 사용되는 외부 입력값을 안전하게 필터링하는 기능이 설계되어 있거나 안전한 외부라이브러리를 사용하도록 설계되어 있는지 확인 ㅇ XML데이터를 조회하는 기능 구현시, XML필터링을 적용하기 위한 코딩규칙이나 안전하게 사용할 수 있는 API에 대한 설명이 개발가이드에 정의되어 있는지 확인 ㅇ XML조회 구문을 변경할 수 있는 입력값을 사용하여 XML조회 구문이 변경되는지를 점검하는 테스트계획의 수립 여부 확인 → 구문 변경 가능 입력값: 쿼리예약어, * [ ] / = @ |
보안대책 (구현단계) |
XQuery 또는 Xpath 쿼리에 사용되는 외부 입력데이터에 대하여 특수문자 및 쿼리 예약어를 필터링하고 파라미터화된 쿼리문을 지원하는 XQuery를 사용한다. |
진단방법 (구현단계) |
- XQuery 삽입 ① XQuery가 실행되는 부분을 확인하고 ② XQuery 쿼리스트링에 사용되는 변수가 외부 입력값 여부를 확인한 후, 변수에 대한 필터링 모듈이 존재하는지 확인한다. 필터링 모듈이 존재하거나 관련 프레임워크에서 적절히 조치할 경우 안전한 것으로 판정한다. - XPath 삽입 ① XPath 객체로 쿼리 스트링이 컴파일 되는 부분을 확인하고, ② XPath 쿼리스트링에 사용되는 변수가 외부 입력값 인지 확인한 후 변수에 대한 필터링 모듈이 존재하는지 확인한다. 필터링 모듈이 존재하거나 관련 프레임워크에서 적절하게 조치된 경우엔 안전하다고 판정한다. |
다. 코드예제
ㅇ 분석
1:
2: String name = props.getProperty("name");
3: .......
4:
5: String es = "doc('users.xml')/userlist/user[uname='"+name+"']";
6: XQPreparedExpression expr = conn.prepareExpression(es);
7: XQResultSequence result = expr.executeQuery();
ㅇ 설명
1. name 검증 어디갔어
ㅇ 수정
1: //
2: String name = props.getProperty("name");
3: .......
4: String es = "doc('users.xml')/userlist/user[uname='$xname']";
5: XQPreparedExpression expr = conn.prepareExpression(es);
6: expr.bindString(new QName("xname"), name, null);
7: XQResultSequence result = expr.executeQuery();
ㅇ 설명
1. 외부로부터 받은 name과 xquery를 바인딩, 매핑해서 사용(sql에서 preparedstatment와 유사함)
ㅇ 분석
//
1:String squery =
2: "for $user in doc(users.xml)//user[username='"
3: + UserTextBox.Text
4: + "'and pass='"
5: + PwdTextBox.Text
6: + "'] return $user";
7:Processor processor = new Processor();
8:XQueryCompiler compiler = processor.NewXQueryCompiler();
9:XdmNode indoc = processor.NewDocumentBuilder().Build(new
Uri(Server.MapPath("users.xml")));
10: using (StreamReader query = new StreamReader(squery))
11: {
12: XQueryCompiler compiler = processor.NewXQueryCompiler();
13: XQueryExecutable exp = compiler.Compile(query.ReadToEnd());
14: XQueryEvaluator eval = exp.Load();
15: eval.ContextItem = indoc;
16: Serializer qout = new Serializer();
17: qout.SetOutputProperty(Serializer.METHOD, "xml");
18: qout.SetOutputProperty(Serializer.DOCTYPE_PUBLIC, "-//W3C//DTD XHTML
1.0 Strict//EN");
19: qout.SetOutputProperty(Serializer.DOCTYPE_SYSTEM,
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
20: qout.SetOutputProperty(Serializer.INDENT, "yes");
21: qout.SetOutputProperty(Serializer.OMIT_XML_DECLARATION, "no");
22: qout.SetOutputWriter(Response.Output);
//
23: eval.Run(qout);27:
}
ㅇ 내용
1. r3하고 r5에서 UserTextBox, PwdTextBox값을 직접 받아 squery를 만듬.
2. 직접 받은 값을 r10에서 Streamreader 객체 생성, 이후 일련의 처리 후 23에서 eval.Run(실행)을 하는데, 이 과정동안 검증 내용이 빠짐
ㅇ 수정
1:String squery =
2: "for $user in doc(users.xml)//user[username='"
3: + UserTextBox.Text
4: + "'and pass='"
5: + PwdTextBox.Text
6: + "'] return $user";
// 문자열 필터링으로 위험한 문자열을 제거해 줍니다.
7: string validatedQuery = squery.Replace('/','*');
8: Processor processor = new Processor();
9: XQueryCompiler compiler = processor.NewXQueryCompiler();
10: XdmNode indoc = processor.NewDocumentBuilder().Build(new
Uri(Server.MapPath("users.xml")));
11:using (StreamReader query = new StreamReader(validatedQuery))
12:{ // tainted value propagated
13: XQueryCompiler compiler = processor.NewXQueryCompiler();
14: XQueryExecutable exp = compiler.Compile(query.ReadToEnd()); //
xquery created
15: XQueryEvaluator eval = exp.Load();
16: eval.ContextItem = indoc;
17: Serializer qout = new Serializer();
18: qout.SetOutputProperty(Serializer.METHOD, "xml");
19: qout.SetOutputProperty(Serializer.DOCTYPE_PUBLIC, "-//W3C//DTD XHTML
1.0 Strict//EN");
20: qout.SetOutputProperty(Serializer.DOCTYPE_SYSTEM,
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
21: qout.SetOutputProperty(Serializer.INDENT, "yes");
22: qout.SetOutputProperty(Serializer.OMIT_XML_DECLARATION, "no");
23: qout.SetOutputWriter(Response.Output);
24: eval.Run(qout);
25:}
ㅇ 설명
1. r7에서 /를 *로 대치하는데.. *도 쿼리 변경 가능 문자 아닌가?
ㅇ 분석
//
1:String nm = props.getProperty("name");
2:String pw = props.getProperty("password");
3:......
4:XPathFactory factory = XPathFactory.newInstance();
5:XPath xpath = factory.newXPath();
6:......
//
7:XPathExpression expr = xpath.compile("//users/user[login/text()='"+nm+"' and
password/text()='"+pw+"']/home_dir/text()");
//
8:Object result = expr.evaluate(doc, XPathConstants.NODESET);
//
9:NodeList nodes = (NodeList) result;
10:for (int i=0; i<nodes.getLength(); i++) {
11: String value = nodes.item(i).getNodeValue();
12: if (value.indexOf(">") < 0) {
//
13: System.out.println(value);
14: }:
15:}
ㅇ 설명
1. r7에서 nm, pw 값 받은거 검증 안하고 쿼리에 던짐
2. r8에서 추가 검증 없이 result에 저장함
3. 그 이후 코드는 뭘 의미하는지 모르겠음. r13에서 결과를 출력하는데 익스플로잇한 로그인 사용자의 ID/PW?
ㅇ 수정
declare variable $loginID as xs:string external;
declare variable $password as xs:string external;
//users/user[@loginID=$loginID and @password=$password]
// XQuery를 이용한 XPath Injection 방지
1:String nm = props.getProperty("name");
2:String pw = props.getProperty("password");
3:Document doc = new Builder().build("users.xml");
//파라미터화된 쿼리가 담겨있는 login.xq를 읽어와서 파라미터화된 쿼리를 생성한다.
4:XQuery xquery = new XQueryFactory().createXQuery(new File("login.xq"));
5:Map vars = new HashMap();
//검증되지 않은 외부값인 nm, pw를 파라미터화된 쿼리의 파라미터로 설정한다.
6:vars.put("loginID", nm);
7:vars.put("password", pw);
// 파라미터화된 쿼리를 실행하므로 외부값을 검증 없이 사용하여도 안전하다.
8:Nodes results = xquery.execute(doc, null, vars).toNodes();
9:for (int i=0; i<results.size(); i++) {
10: System.out.println(results.get(i).toXML());21:
11:}
ㅇ 설명
1. 로그인 쿼리를 불러와서(파라미터화) iD/PW값을 대입하는 방법으로 보임.
+
2. 아니면 ID/PW값에서 구문 분리 유발 가능성이 있는 문자열 필터링 후 로그인하는 방식 사용도 가능함
ㅇ 분석
1:public static void main(String[] args) throws Exception {
2: if (args.length <= 0) {
3: System.err.println("가격을 검색할 식품의 이름을 입력하세요.");
4: return;
5: }
6: String name = args[0];
7: DocumentBuilder docBuilder =DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = docBuilder.parse("http://www.w3schools.com/xml/simple.xml");
8: XPath xpath = XPathFactory.newInstance().newXPath();
//
//
9: NodeList nodes = (NodeList) xpath.evaluate("//food[name='" + name + "']/price",
doc, XPathConstants.NODESET);
10: for (int i = 0; i < nodes.getLength(); i++) {
11: System.out.println(nodes.item(i).getTextContent());
12: }
13:}
ㅇ 설명
1. name값에 대한 검증 없이 쿼리 실행
ㅇ 수정
1:public static void main(String[] args) throws Exception {
2: if (args.length <= 0) {
3: System.err.println("가격을 검색할 식품의 이름을 입력하세요.");
4: return;
5: }
//프로그램의 커맨드 옵션으로 입력되는 외부값 name에서 XPath 구문을 조작할 수
// 는 문자를 제거하는 검증을 수행하여 안전하다.
6: String name = args[0];
7: if (name != null) {
8: name = name.replaceAll("[()\\-'\\[\\]:,*/]", "");
9: }
10: DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
11: Document doc = docBuilder.parse("http://www.w3schools.com/xml/simple.xml");
12: XPath xpath = XPathFactory.newInstance().newXPath();
13: NodeList nodes = (NodeList) xpath.evaluate("//food[name='" + name + "']/price",
doc, XPathConstants.NODESET);
14: for (int i = 0; i < nodes.getLength(); i++) {
15: System.out.println(nodes.item(i).getTextContent());
16: }
ㅇ 설명
1. 필터링 후 대치 수행
ㅇ 분석
1:string acctID = Request["acctID"];
2:string query = null;
3:if(acctID != null)
4:{
5: StringBuffer sb = new StringBuffer("/accounts/account[acctID='");
6: sb.Append(acctID);
7: sb.Append("']/email/text()");
8: query = sb.ToString();
9:}
10: XPathDocument docNav = new XPathDocument(myXml);
11:XPathNavigator nav = docNav.CreateNavigator();
//
12:nav.Evaluate(query);
ㅇ 내용
1.acctID 입력값 검증 안하고 경로에 append해서 사용함.
ㅇ 수정
1:string xpath = "/accounts/account[@acctID=$acctID]/email/text()";
2:XPathExpression expr = DynamicContext.Compile(xpath);
3:DynamicContext ctx = new DynamicContext();
4:ctx.AddVariable("acctID", AccountIDTextBox.Text);
5:expr.SetContext(ctx);
6:XPathNodeIterator data = nav.Select(expr);
ㅇ 내용
1. 쿼리 형태를 r1과 같이 설정해놓고, acctID값을 받아 사용하여 쿼리구조변경이 없음
끝.
'자격 > SW보안약점진단원' 카테고리의 다른 글
구현단계 보안약점 제거 기준-크로스사이트 요청 위조 (0) | 2023.07.04 |
---|---|
구현단계 보안약점 제거 기준-LDAP 삽입 (0) | 2023.07.04 |
구현단계 보안약점 제거 기준-부적절한 XML 외부개체 참조 (0) | 2023.07.04 |
구현단계 보안약점 제거 기준-신뢰되지 않는 URL주소로 자동접속 연결 (0) | 2023.07.04 |
구현단계 보안약점 제거 기준-위험한 형식 파일 업로드 (0) | 2023.07.04 |