Hi,

I plan to release a DSA fixing this issue with the attached patch.
Please upload a new version to sid containing the fix.

Cheers,
-- 
Raphael Geissert - Debian Developer
www.debian.org - get.debian.net
Fix CVE-2009-1629: weak session id generation
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.

diff -urpN ajaxterm-0.10-4.orig/ajaxterm.js ajaxterm-0.10-4/ajaxterm.js
--- ajaxterm-0.10-4.orig/ajaxterm.js	2006-10-28 21:52:39.000000000 -0500
+++ ajaxterm-0.10-4/ajaxterm.js	2010-02-04 19:45:11.000000000 -0600
@@ -3,7 +3,24 @@ ajaxterm.Terminal_ctor=function(id,width
 	var ie=0;
 	if(window.ActiveXObject)
 		ie=1;
-	var sid=""+Math.round(Math.random()*1000000000);
+
+	var sid="";
+
+	for (var i=0; i < 128; 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;
+	}
+
 	var query0="s="+sid+"&w="+width+"&h="+height;
 	var query1=query0+"&c=1&k=";
 	var buf="";
diff -urpN ajaxterm-0.10-4.orig/ajaxterm.py ajaxterm-0.10-4/ajaxterm.py
--- ajaxterm-0.10-4.orig/ajaxterm.py	2006-10-28 21:52:39.000000000 -0500
+++ ajaxterm-0.10-4/ajaxterm.py	2010-02-04 20:01:50.000000000 -0600
@@ -2,7 +2,14 @@
 
 """ Ajaxterm """
 
-import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd
+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
@@ -483,30 +490,61 @@ class AjaxTerm:
 		self.mime['.html']= 'text/html; charset=UTF-8'
 		self.multi = Multiplex(cmd)
 		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:
@@ -515,9 +553,23 @@ class AjaxTerm:
 				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()

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to