이 글은 2018.05.15에 github pages를 이용해 만들어진 블로그에서 작성 된 글입니다
저번 글에 이어서 파싱 관련 글만 계속 쓰는 것 같다. 이번에는 교내에서 과제제출, 과목공지, 수업자료 업로드 등의 목적으로 사용되는 Blackboard 서비스에서 과목 데이터를 받아오기 위해 파싱을 진행했다. ...
이 글에서 개인정보유출 또는 보안상 문제가 될 우려가 있는 부분은 xxx나 … 또는 ~ 등을 이용하여 임의로 삭제되어있습니다.
저번 글에 이어서 파싱 관련 글만 계속 쓰는 것 같다.
이번에는 교내에서 과제제출, 과목공지, 수업자료 업로드 등의 목적으로 사용되는 Blackboard 서비스에서 과목 데이터를 받아오기 위해 파싱을 진행했다.
아래 로그인 화면을 거치면
아래와 같이 과목 목록이 있는 페이지로 이동한다
현재 수강중인 과목의 정보를 가져오려면 먼저 로그인을 해야하기 때문에 크롬 개발자 도구를 이용해서 form의 구조와 어디로 post data를 전달하는지(form tag의 action 속성)를 알아봤다.
user_id로는 학번이 들어가고 password로는 패스워드가 들어가기 때문에 채워서 requests.post를 호출해보면
1 2 3 4 5 6 7 8
import requests data = { 'user_id': '2015xxxx', 'password': 'xxxxxxxxxxxxx', } res = requests.post('http://xxx.xxx.ac.kr/webapps/login/', data=data) with open('test.html', 'w') as f: f.write(res.content.decode())
당연하게도 실패한다
위 html 코드를 보면 알겠지만 form에 onsubmit 속성이 존재하고, submit 되기 전에 validate_form function이 호출되는 것을 볼 수 있다. validate_form이 뭔지 확인해보자.
if ( !skipEncoding ) // Only challenge-response and b64 for legacy auth { if ( useChallengeResponse ) { return validate_form_with_challenge( form ); } else { return validate_form_no_challenge( form ); } } }
!skipEncoding 값과 useChallengeResponse param 값에 의해서 어떻게 처리될지 결정이 된다. 위 html 코드에서 onsubmit 속성을 보면
두 변수를 이용하는 곳 중 skipEncoding은 항상 false여야 하므로 (true이면 우리가 requests로 보냈었던 요청과 다르지 않으므로) useChallengeResponse가 false일 경우인 validate_form_no_challange(form); 부분을 확인해보도록 하자.
그리고 여기에서 사용되는 base64encode(), b64_unicode() 함수는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Converts the input string into a base64 string functionbase64encode(str) { // Break up into 3 character quantums and convert var i, nFrom, nTo; var sRet = ""; for (i=0; i<str.length; i+=3) { nFrom = i; nTo = (i+3 < str.length)? i+3 : str.length; sRet += base64encode_quantum(str.substring(nFrom,nTo)); } return sRet; }
/* * Convert a string to an array of little-endian words * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. */ functionstr2binl(str) { var bin = Array(); var mask = (1 << chrsz) - 1; for(var i = 0; i < str.length * chrsz; i += chrsz) bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); return bin; }
defstr2binl(s):# s == "str" b_array = [] # 원본 js 코드의 bin i_max = chrsz * ((len(s)*chrsz-1) // chrsz) idx_max = (i_max >> 5) + 1 for i in range(idx_max): # 원본 js에는 없는 루틴이지만 이 코드 없이 실행하면 파이썬에서는 index out of range 발생 b_array.append(0) # 0 == or 연산에 대한 항등원 mask = (1<<chrsz) - 1 for i in range(0, len(s)*chrsz, chrsz): b_array[i>>5] |= (ord(s[i//chrsz]) & mask) << (i%32) return b_array
defbinl2b64(binarray): binarray_c = binarray[:] # deep copy tab = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' result_str = ''# 원본 코드의 'str' # js에서 index 보다 큰 위치에 접근하면 0을 반환 for i in range(0, len(binarray_c)*4, 3): try: op1 = (((binarray_c[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) except IndexError: op1 = 0 try: op2 = (((binarray_c[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) except IndexError: op2 = 0 try: op3 = ((binarray_c[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF) except IndexError: op3 = 0 triplet = op1 | op2 | op3 for j in range(4): if i * 8 + j * 6 > len(binarray_c)*32: result_str += b64pad else: result_str += tab[(triplet >> 6 * (3 - j)) & 0x3F] return result_str
<BODYBGCOLOR='#FFFFFF'LINK='#000000'ALINK='#000000'> <br> <br> <br> <br> <divstyle="text-align: center;"> <hrwidth='350'height='5'> <br> <strong>You are being redirected to another page</strong> <p> <strong>Please Wait...</strong> <br> <br> <hrwidth='350'height='5'> <br> <AHREF='.../webapps/portal/frameset.jsp'> <strong>Click here to access the page to which you are being forwarded.</strong> </A> </div> </BODY>
</HTML>
로그인 후에는 쿠키를 유지하고 /webapps/portal/frameset.jsp로 이동해야한다.
data = { 'user_id': '2015xxxx', 'encoded_pw': encoded_pw, 'encoded_pw_unicode': encoded_pw_unicode,
}
res = requests.post('.../webapps/login/', data=data) res_ = requests.get('.../webapps/portal/frameset.jsp', cookies=res.cookies) with open('test.html', 'w') as f: f.write(res_.content.decode())
실행하면
성공이다.
2018-12-02 추가: 교내 서버로 운영되던 서비스가 최근에 클라우드로 이전하면서 해당 코드로 동작하지 않을 수 있습니다. 여기에서 설명했던 일부 보안 문제는 이 과정에서 해결되었으니 다행입니다