Package: ajaxterm / 0.10-12

25_CVE-2009-1629.diff Patch series | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
Description: Security fix for CVE-2009-1629 (generates session IDs with predictable random numbers)
 Use a cookie with a strong random name and a strong random value for each
 user to protect from session id hijacking attacks.  The browser-generated
 session id is now stronger but only used to separate multiple sessions
 of the same ip.
 Additionally, a limiting mechanism sets a maximum of 20 simultaneous
 sessions in total and 4 maximum sessions per connecting ip. The
 X-Forwarded-For header is only honoured if the real remote ip is 127.0.0.1.
Author: Raphael Geissert <geissert@debian.org>
Origin: http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=75;filename=CVE-2009-1629.patch;att=1;bug=528938
Debian-Bug: http://bugs.debian.org/528938

--- a/ajaxterm.js
+++ b/ajaxterm.js
@@ -6,7 +6,22 @@
 		ie=1;
 	if (navigator.userAgent.indexOf("WebKit") >= 0)
 		webkit=1;
-	var sid=""+Math.round(Math.random()*1000000000);
+	var sid="";
+
+	for (var i=0; i < 255; i++) {
+	    var r = 0;
+	    // now get a random number between 0 and 255
+	    // numbers not in the range are intentionally discarded
+	    // as it reduces the chance of predicting the seed, by not
+	    // using all of the numbers generated by the PRNG
+	    do {
+		r = Math.round(Math.random()*1000);
+	    } while(r >= 255);
+	    r = r.toString(16);
+	    if (r.length == 1)
+		r = "0"+r;
+	    sid += "%" + r;
+	}
 
         if (width==0) {
                 width=80;
--- a/ajaxterm.py
+++ b/ajaxterm.py
@@ -8,8 +8,14 @@
 except:
 	pass
 
-import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd
-from datetime import datetime
+import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd,Cookie
+from datetime import datetime, timedelta
+
+try:
+	from hashlib import sha1
+except ImportError:
+	import sha
+	sha1 = sha.new
 
 os.chdir(os.path.normpath(os.path.dirname(__file__)))
 # Optional: Add QWeb in sys path
@@ -517,30 +523,61 @@
 		self.multi = Multiplex(cmd,serverport)
 		self.reaper = Reaper(self.multi)
 		self.session = {}
+		self.session_ip = {}
+		self.sessions_limit = 20
+		self.sessions_user_limit = 4
+		m = sha1()
+		m.update(os.urandom(128))
+		self.cookie_name = m.hexdigest()
 	def __call__(self, environ, start_response):
 		req = qweb.QWebRequest(environ, start_response,session=None)
 		if req.PATH_INFO.endswith('/u'):
+			req.response_headers['Content-Type']='text/xml'
+			uid=""
+			if self.cookie_name not in req.request_cookies:
+				req.write('<?xml version="1.0"?><idem></idem>')
+				return req
+			uid = req.request_cookies[self.cookie_name].value
 			s=req.REQUEST["s"]
 			k=req.REQUEST["k"]
 			c=req.REQUEST["c"]
 			w=req.REQUEST.int("w")
 			h=req.REQUEST.int("h")
-			if s in self.session:
-				term=self.session[s]
+			ip="unknown"
+			if environ.has_key("REMOTE_ADDR"):
+				ip=environ['REMOTE_ADDR']
+				if ip == "127.0.0.1" and environ.has_key("HTTP_X_FORWARDED_FOR"):
+				    ip=environ["HTTP_X_FORWARDED_FOR"]
+
+			if (uid+s) in self.session:
+				term=self.session[uid+s]
+				req.response_cookies.load(req.request_cookies[self.cookie_name].OutputString())
+				req.response_cookies[self.cookie_name]['expires'] = datetime.utcnow()+timedelta(seconds=60)
 			else:
 				if not (w>2 and w<256 and h>2 and h<100):
 					w,h=80,25
-				term=self.session[s]=self.multi.create(w,h)
+				# check if there aren't too many open sessions
+				if len(self.session) < self.sessions_limit:
+					count=0
+					for i in self.session_ip.keys():
+						if self.session_ip[i] == ip:
+							count+=1
+					if count <= self.sessions_user_limit:
+						term=self.session[uid+s]=self.multi.create(w,h)
+						self.session_ip[uid+s]=ip
+					else:
+						req.write('<?xml version="1.0"?><idem></idem>')
+						return req
 			if k:
 				self.multi.proc_write(term,k)
 			time.sleep(0.002)
 			dump=self.multi.dump(term,c)
-			req.response_headers['Content-Type']='text/xml'
 			if isinstance(dump,str):
 				req.write(dump)
 				req.response_gzencode=1
 			else:
-				del self.session[s]
+				del self.session[uid+s]
+				del self.session_ip[uid+s]
 				req.write('<?xml version="1.0"?><idem></idem>')
 #			print "sessions %r"%self.session
 		else:
@@ -549,9 +586,23 @@
 				req.response_headers['Content-Type'] = self.mime.get(os.path.splitext(n)[1].lower(), 'application/octet-stream')
 				req.write(self.files[n])
 			else:
+				if self.cookie_name not in req.request_cookies:
+				    self.genSidCookie(req)
 				req.response_headers['Content-Type'] = 'text/html; charset=UTF-8'
 				req.write(self.files['index'])
 		return req
+	def genSidCookie(self, req):
+		m = sha1()
+		m.update(os.urandom(160))
+		req.response_cookies[self.cookie_name] = m.hexdigest()
+		# try to set httponly if supported (added in 2.6)
+		try:
+		    req.response_cookies[self.cookie_name]['httponly'] = 1
+		except (Cookie.CookieError):
+		    pass
+		req.response_cookies[self.cookie_name]['path'] = req.PATH_INFO
+		req.response_cookies[self.cookie_name]['expires'] = datetime.utcnow()+timedelta(seconds=60)
+		return req
 
 def main():
 	parser = optparse.OptionParser()