<p>I have a legacy application, that I cannot change, with a login system and I am trying to write a small Go service to add some functionality to the legacy app. Imagine that the legacy app runs at "/" and the Go service will run at "/goservice". For the convenience of the users I think that the Go service should use the same authentication mechanism as the legacy app.</p>
<p>As far as I know, the legacy app creates two cookies on a successful login. One that contains the username and another that contains a "session" value which is an md5 digest combining different values (username, password hash and a masked IP address).</p>
<p>My question is, how can I know in my Go service that a user has already logged in via the main legacy application?</p>
<p>After reading <a href="https://mschoebel.info/2014/03/09/snippet-golang-webapp-login-logout/">some</a> <a href="https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/06.1.html">tutorials</a>, the solution I thought is: every time someone tries to access content that is under "/goservice", I use <code>http.Request.Cookie</code> in my Go handlers and get those two cookies that are set by the legacy app. Then I try to recreate the md5 value of the "session" cookie by using the username (taken from the other cookie) and info from the database. (The database does not store any actual sessions but it stores the password hash.) By combining the username, the password hash and the IP of the http request (after masking it) I can recreate the same md5 digest value that the "session" cookie has. If the value I generate matches the cookie then the user is allowed to view the Go service content, otherwise they are redirected to the legacy app login screen.</p>
<p>While this sounds like it would work in theory, I do not have any experience with handling authentication in Go so I thought I'd ask here before I attempt to do something horribly wrong, overly complex or insecure.</p>
<hr/>**评论:**<br/><br/>earthboundkid: <pre><p>This is semi-horrifying. There are definitely less secure things out there in the world, but this is not terribly secure. I suggest you start by ignoring Go and just reading about how session management in HTTP is supposed to be done. The obvious starting point is <a href="https://en.wikipedia.org/wiki/Session_(computer_science)" rel="nofollow">Wikipedia</a>.</p>
<p>Changing from md5 to bcrypt is a no brainer. It's a drop in replacement. Unfortunately, you'll still be left without a "salt" to your hash, so an attacker could still try to brute force a collision.</p>
<p>Other changes would be harder for the legacy system, but are worth considering. Why is the session id being generated from the request instead of being a random number that is generate on the server and stored in a database or other key-value store (memcached/redis)? I imagine the answer to be either a) we didn't think of that or b) performance. Performance, however, really only comes into play if you have a huge number of clients, and in that case, you really can't afford to be doing security wrong.</p>
<p>I understand that the client is a limiting factor, but if the legacy application is as bad as it seems, you may need to consider firing the client. </p></pre>nesigma: <pre><blockquote>
<p>just reading about how session management in HTTP is supposed to be done. The obvious starting point is Wikipedia</p>
</blockquote>
<p>Personally I find that I learn better by examples. It's very easy to read how it's "supposed" to be done, yet the reality is usually quite different. I'd rather read secure and production-ready code or examples.</p>
<blockquote>
<p>Changing from md5 to bcrypt is a no brainer. It's a drop in replacement.</p>
</blockquote>
<p>I've never tried to transfer code from md5 to bcrypt but I imagine that in practice it shouldn't be that simple especially when there's hundreds of users that might be affected if things go wrong. But in the end I do not own the legacy app code and I cannot do any changes to it. </p>
<blockquote>
<p>I imagine the answer to be either a) we didn't think of that or b) performance.</p>
</blockquote>
<p>Only the original developers of the legacy app can answer that question.</p>
<blockquote>
<p>but if the legacy application is as bad as it seems, you may need to consider firing the client.</p>
</blockquote>
<p>It's old and clunky but it seems to get the job done. It has not caused me personally any problems and as long as I stay within the limits of Go hopefully that will continue to be the case.</p>
<p>The discussion about the horribleness of the legacy app is fun and all but any thoughts on my actual question/solution? Thanks.</p></pre>barsonme: <pre><blockquote>
<p>I've never tried to transfer code from md5 to bcrypt but I imagine that in practice it shouldn't be that simple especially when there's hundreds of users that might be affected if things go wrong. But in the end I do not own the legacy app code and I cannot do any changes to it.</p>
</blockquote>
<p>Force users to sign in again. (Does the legacy app allow that?) When the user signs in hash their password with bcrypt (or scrypt!) and store that in the database. All new users get their password hashed with bcrypt (or scrypt!). You're gonna need to add two columns to your table: one for a flag indicating whether the password is hashed with bcrypt (or scrypt!) and one for the salt.</p></pre>nesigma: <pre><blockquote>
<p>Force users to sign in again. (Does the legacy app allow that?)</p>
</blockquote>
<p>1) How can you force users to sign in again? I mean how would you go about that programmatically? If sessions were stored on the server I'd imagine it would be simple to delete the sessions but remember that the legacy app uses those two cookies.</p>
<p>2) What if a user enters a wrong password when the change md5 -> bcrypt is happening? </p>
<p>The cherry on the pie is that the legacy app does not have an automatic password recovery system (at least as far as I know) so if say a thousand users lose their passwords because of the md5 -> bcrypt change It won't be a pleasant situation.</p>
<p>P.S. Bashing the legacy app on reddit is surprisingly entertaining.</p></pre>barsonme: <pre><blockquote>
<p>How can you force users to sign in again? I mean how would you go about that programmatically? If sessions were stored on the server I'd imagine it would be simple to delete the sessions but remember that the legacy app uses those two cookies.</p>
</blockquote>
<p>Gag. (Sorry for harping on the legacy app, but wow...)</p>
<p>I suppose, then, you can't unless you were to require a change in the cookies (at that point you might as well rework the entire auth system) or simply not allow the user to do anything (i.e., only show the login screen) until the flag in the DB was flipped.</p>
<blockquote>
<p>2) What if a user enters a wrong password when the change md5 -> bcrypt is happening?</p>
</blockquote>
<p>I assumed you'd validate the password <em>before</em> hashing it with bcrypt.</p>
<blockquote>
<p>The cherry on the pie is that the legacy app does not have an automatic password recovery system (at least as far as I know) so if say a thousand users lose their passwords because of the md5 -> bcrypt change It won't be a pleasant situation.</p>
</blockquote>
<p>Heh. Backup the database first? Or, better solution -- write the changes into a new table (probably what I should've suggested in the first place) and then once everybody (or a sizable amount) has transferred over, use that table while on-the-fly updating for new people.</p>
<p>Might take some sample testing to get down, but it's been done before. (Back when everybody had to change over to bcrypt in the first place.)</p>
<blockquote>
<p>P.S. Bashing the legacy app on reddit is surprisingly entertaining.</p>
</blockquote>
<p>:D</p></pre>nesigma: <pre><blockquote>
<p>I assumed you'd validate the password before hashing it with bcrypt.</p>
</blockquote>
<p>Well the suggestion from <a href="/u/earthboundkid" rel="nofollow">/u/earthboundkid</a> was that switching to bcrypt is a "no brainer" which I assume means trivial and transparent aka a user simply logins as usual and has no clue that now bcrypt is used. So how would someone validate the password in this case?</p>
<blockquote>
<p>Backup the database first? Or, better solution -- write the changes into a new table (probably what I should've suggested in the first place) and then once everybody (or a sizable amount) has transferred over, use that table while on-the-fly updating for new people.
Might take some sample testing to get down, but it's been done before. (Back when everybody had to change over to bcrypt in the first place.)</p>
</blockquote>
<p>So yeah, as I've said before, it might be considered a "no brainer" in theory but in practice things are quite different. It makes me glad I don't have to deal with this problem. Thank you for the suggestions nonetheless. </p></pre>earthboundkid: <pre><blockquote>
<p>Well the suggestion from <a href="/u/earthboundkid" rel="nofollow">/u/earthboundkid</a> was that switching to bcrypt is a "no brainer" which I assume means trivial and transparent aka a user simply logins as usual and has no clue that now bcrypt is used. So how would someone validate the password in this case?</p>
</blockquote>
<p>This is SOP for pretty much any auth system that works. Create a new database column called "auth_type" and set it to MD5 to start with. After that, when a user logs in, check their auth_type. If it's MD5 and the password checks out according to the old system, use the password to generate a new bcrypt hash and write auth_type = 'bcrypt' and the new hash to the database. It's also a good idea to add a column for a secret salt at that point.</p>
<p>Again, this is a standard practice for pretty much all auth systems that work. It has nothing to do with Go and everything to do with normal security practices. Literally every language used on the web has multiple frameworks to automate this process in various ways. </p></pre>barsonme: <pre><p>Well, it is horribly insecure.</p>
<p>You've just leaked the hashed password from your database. Plus, MD5 collisions aren't (comparatively) difficult when you can manipulate the data.</p>
<p>If you absolutely cannot modify the old application (which I suggest you do since it's <em>not</em> secure) then I suppose you'll just have to accept the old application's cookies.</p>
<p>But the best scenario is to use more secure authentication like HTTP-Only, Secure cookies <a href="https://godoc.org/golang.org/x/crypto/nacl/secretbox" rel="nofollow">generated with AEAD</a> <em>or</em> use <a href="https://github.com/golang/oauth2" rel="nofollow">OAuth</a>.</p>
<p>I'm curious, how do you hash the passwords you store? </p></pre>nesigma: <pre><blockquote>
<p>You've just leaked the hashed password from your database.</p>
</blockquote>
<p>How?</p>
<blockquote>
<p>then I suppose you'll just have to accept the old application's cookies.</p>
</blockquote>
<p>Does that mean that my solution is good enough as far as the Go service is concerned? (assuming that I cannot change the legacy application.)</p>
<blockquote>
<p>But the best scenario is to use more secure authentication like HTTP-Only, Secure cookies generated with AEAD or use OAuth</p>
</blockquote>
<p>What is HTTP-Only? I wish I could change the legacy app to use another method but I cannot.</p></pre>barsonme: <pre><blockquote>
<p>How?</p>
</blockquote>
<p>If know the user name and time stamp then the cookie is essentially md5(password_hash).</p>
<blockquote>
<p>Does that mean that my solution is good enough as far as the Go service is concerned? (assuming that I cannot change the legacy application.)</p>
</blockquote>
<p>If the Go application can recreate what the legacy application does, then yes. But I'm still concerned about the security of the entire thing.</p>
<blockquote>
<p>What is HTTP-Only? I wish I could change the legacy app to use another method but I cannot.</p>
</blockquote>
<p>It's a setting in the cookie that disallows JavaScript from sending the cookie. Secure is a setting that only sends the cookie over TLS (which I assume you're using).</p></pre>nesigma: <pre><blockquote>
<p>If know the user name and time stamp then the cookie is essentially md5(password_hash).</p>
</blockquote>
<p>What time stamp? Do you mean the IP? I suppose it is md5(password_hash + masked_IP). I don't know how problematic is that.</p>
<blockquote>
<p>It's a setting in the cookie that disallows JavaScript from sending the cookie. Secure is a setting that only sends the cookie over TLS (which I assume you're using).</p>
</blockquote>
<p>I didn't know about those. The domain is using TLS but I am not sure about the cookies. I will look it up thanks. Of course I cannot change the legacy cookies but it's good to know for the future.</p>
<blockquote>
<p>If the Go application can recreate what the legacy application does, then yes. </p>
</blockquote>
<p>Okay then at least I know that my Go code is fine. Thank you.</p>
<blockquote>
<p>But I'm still concerned about the security of the entire thing.</p>
</blockquote>
<p>Yeah, it's old and clunky but it gets the job done. I don't even want to try XSS through the inputs that the legacy app uses. At least the Go service won't have those problems.</p></pre>nesigma: <pre><blockquote>
<p>I'm curious, how do you hash the passwords you store?</p>
</blockquote>
<p>I looked it up. The legacy app seems to be using md5(username + password). I suppose bcrypt is the better alternative.</p></pre>barsonme: <pre><p>That's terrifying.</p>
<p>Yeah, the database needs to be moved over to scrypt or bcrypt ASAP.</p></pre>nesigma: <pre><blockquote>
<p>Yeah, the database needs to be moved over to scrypt or bcrypt ASAP.</p>
</blockquote>
<p>I'll try to convince the owners to move to bcrypt but I think I know the answer already. Thank you for the replies.</p></pre>barsonme: <pre><p>Honestly, I'd let them know just how incredibly unsafe their app is. Who knows -- you could get some business out of it. But at the very least it's a courtesy.</p></pre>mc_hammerd: <pre><p>the trick is to integrate the go app into your app on the backend not the frontend</p>
<p>add /goservice to the legacy app and have it run "mygoapp -dostuff foo bar baz"</p>
<p>while the legacy app handles auth</p>
<p>alternatively you could make the goservice run 100% of the time. and you could use like nanomsg/sockets/a file handler/message queue to pass data between the two, but i would think the legacy app still handles the auth. some ppl like using nginx as a proxy server here (if the go service serves html or REST/soap/ajax routes)</p></pre>nesigma: <pre><blockquote>
<p>the trick is to integrate the go app into your app on the backend not the frontend
add /goservice to the legacy app and have it run "mygoapp -dostuff foo bar baz"
while the legacy app handles auth</p>
</blockquote>
<p>I cannot do any changes to the legacy app.</p>
<blockquote>
<p>alternatively you could make the goservice run 100% of the time.</p>
</blockquote>
<p>Actually that is the plan. Since the legacy app uses apache2 as a proxy I can easily make it so that everything that hits "/goservice" is served by the Go service instead. </p>
<blockquote>
<p>and you could use like nanomsg/sockets/a file handler/message queue to pass data between the two</p>
</blockquote>
<p>I don't think there's a need for such complexity as I am lucky enough that the go service will offer separate functionality. There might be a need to get 2-3 things from the legacy app database but that's about it.</p>
<p>What do you think of the approach of trying to match the legacy app cookie value in my Go handlers as a way to keep the same login system?</p></pre>metamatic: <pre><blockquote>
<p>I cannot do any changes to the legacy app.</p>
</blockquote>
<p>You don't have to. You already said it's reverse proxied using Apache. You could make your Go app reverse proxy it instead. Your Go app would grab everything under "/goservice", and pass every other HTTP request on to the legacy app -- except, your new app would handle all the authentication with the end user, and do it securely. The new app would then fake up a login to the old app, and maintain the old app's cookies internally.</p></pre>nesigma: <pre><p>Indeed, that is a good plan. Nevertheless in practice that would mean downtime for the users while the go proxy app would be deployed and tested not to mention that it's not trivial to write. I think I'll stick to just the go service instead of trying to patch up the legacy app. But again nice idea.</p></pre>earthboundkid: <pre><p>The app is insecure. Period. It is trivial to hack MD5. That's fine if it's a bulletin board or some other non-critical app. But at the same time, you can't have 5 minutes of downtime? That makes no sense. Either it's not critical and the security issue isn't a big deal, or it is critical and the security issue is a big deal. Either way, you can spare five minutes of downtime.</p></pre>nesigma: <pre><blockquote>
<p>you can't have 5 minutes of downtime? That makes no sense.</p>
</blockquote>
<p>Just transferring what I've been told by the owners. Also proxying or disturbing the flow of the app in any way, downtime or not is not very desirable either. But in the end this is not about the legacy app but about my approach in the go service which is reverse engineering the legacy app cookie code as <a href="/u/mc_hammerd" rel="nofollow">/u/mc_hammerd</a> brilliantly named it.</p></pre>mc_hammerd: <pre><p>the easier solution is to just make the users login again to /goservice :p</p>
<p>but yea you if you want to reverse engineer the cookie code no problem :)</p></pre>nesigma: <pre><blockquote>
<p>the easier solution is to just make the users login again to /goservice :p</p>
</blockquote>
<p>Yeah! That was my first thought. I was even thinking to use gorilla securecookie to keep it simple like in that <a href="https://mschoebel.info/2014/03/09/snippet-golang-webapp-login-logout/" rel="nofollow">article</a> (though I am not sure how secure that would be being client-side only). Other people have proposed other good solutions too like auth2 and server side sessions.</p>
<p>The main reason I am avoiding two logins is because it is not very desirable (by the owners) and thus I am just trying to make it as seamless as possible.</p>
<p>The second reason is that since my experience with authentication is limited (I've always used ready-made solutions from frameworks in other languages) so I am concerned about how good/secure my own solution will be. Naturally, limited experience makes something appear bigger and more complex on someone's head than it might actually be.</p>
<p>Nevertheless I might end up doing two logins in the end. As people have pointed out, the existing system is already insecure as it is. By making a new authentication system in the Go service at least, I'll get the benefits of something more secure (hopefully) while at the same time I will learn a lot of things since with Go I have to reach the lower level areas of the code. Having that new authentication system will also give the benefit of maybe using it in the future to proxy the legacy app or on a new project and the learning experience is very valuable so it's like two birds with one stone.</p>
<blockquote>
<p>but yea you if you want to reverse engineer the cookie code no problem :)</p>
</blockquote>
<p>Yeah, that is what I will attempt first and that was the main question of this thread as I was not quite sure how correct my approach is. Apparently my approach is fine but there are bigger problems that have to do with the legacy app.</p>
<p>Regardless, this thread has helped me learn, understand things better and finalize my decisions so I thank you and everyone else for your time.</p></pre>earthboundkid: <pre><p>ISTM, the legacy app is completely insecure (MD5 has been a security worst practice since 2005), so it should be removed from the public internet. If the constraint is that you can't modify the code for the legacy app in any way, put a new app with its own authentication system onto the internet and have it proxy calls to the legacy system on a private network. Users will authenticate to the new system, and if the calls pass inspection for the new system, the new system will make an insecure call into the old system. The old system should not be on the public internet because breaking MD5 costs about <a href="http://natmchugh.blogspot.com/2015/02/create-your-own-md5-collisions.html" rel="nofollow"><$0.65</a> nowadays.</p></pre>: <pre><p>[deleted]</p></pre>nesigma: <pre><p>The legacy app creates two cookies on a successful login. One that is named "username" and holds the username of the logged in user and a second one named "session" that holds an md5 digest value. I don't think there is anything else.</p></pre>: <pre><p>[deleted]</p></pre>nesigma: <pre><blockquote>
<p>the session must be stored somewhere on the disk if not in the database.</p>
</blockquote>
<p>That "session" cookie is stored on the user's disk after they login. Is that what you mean? The database does not store any other sessions as far as I know.</p>
<p>I suppose that means that the legacy app is using "client-side sessions". </p></pre>kadema: <pre><p>I had such a problem and what worked for me was generating a sha1 token on the server, storing this token in redis. We can then check if the user is authenticated by checking this token and generating the necessary cookies.</p></pre>nesigma: <pre><blockquote>
<p>We can then check if the user is authenticated by checking this token and generating the necessary cookies.</p>
</blockquote>
<p>That sounds like an interesting approach. So my Go service could produce a sha1 token which I could store say on boltdb. I didn't quite get the second part though. Could you elaborate please? </p></pre>kadema: <pre><p>ELI5 version : You already have your token in boltdb(user logged in to xyz.com/ but you also need to authenticate for xyz.com/goservice. Since the user is already authenticated for xyz.com/, when they navigate to xyz.com/goservice, you check for the auth token in boltdb. If it exists and is valid, you check that they came from xyz.com/ (if required). If everything checks out, you can generate auth cookies or whatever you use for xyz.com/goservice. With this aproach, you can decouple the two apps, maintain each with their own auth systems or however your spec requires. </p></pre>nesigma: <pre><blockquote>
<p>You already have your token in boltdb(user logged in to xyz.com/</p>
</blockquote>
<p>No that is not the case. The legacy app creates two cookies on successful login and that's about it. I cannot do any changes on the legacy app. So I guess this idea won't work for my case. Thank you anyway.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传