You are not logged in.
Hi AB,
I was looking for a way to be able to manage access prohibited.
There is' a way to know on the server when a call is prohibited?
I would love to be able to have all the call to extract who is logging, that url was calling, his ip.
All this in order to realize two things: managing the attacks (ip ban with external firewall) may notify the user attempts wrong.
Tnx
Offline
I've added TSQLRestServer.OnSessionFailed callback in addition to OnSessionCreate/OnSessionClosed.
It would be fired when authentication failed.
See http://synopse.info/fossil/info/1cc4677e59
Offline
Tnxs
Tomorrow I try
Offline
there is something wrong...
// 2. handle security
if (not Ctxt.Authenticate) or
((Ctxt.Service<>nil) and
not (reService in Call.RestAccessRights^.AllowRemoteExecute)) then
Ctxt.AuthenticationFailed else
Ctxt.AuthenticationFailed never fire.
I suspect that the function ctxt.authenticate returns true when it should return false.
I have no patch available now
Offline
I rewrote the function, as I think should be its logic
function TSQLRestServerURIContext.Authenticate: boolean;
var aSession: TAuthSession;
i: integer;
begin
if Server.HandleAuthentication then
begin
result := false;
Session := CONST_AUTHENTICATION_SESSION_NOT_STARTED;
Server.fSessions.Lock;
try
aSession := nil;
if Server.fSessionAuthentication<>nil then
begin
for i := 0 to length(Server.fSessionAuthentication)-1 do
begin
aSession := Server.fSessionAuthentication[i].RetrieveSession(self);
if aSession<>nil then
begin
{$ifdef WITHLOG}
log.Log(sllUserAuth,'%/%',[aSession.User.LogonName,aSession.ID],self);
{$endif}
SetString(fSessionAccessRights,PAnsiChar(@aSession.fAccessRights),
sizeof(TSQLAccessRights)); // override access rights
Call^.RestAccessRights := pointer(fSessionAccessRights);
result:=true;
exit;
end;
end;
end;
finally
Server.fSessions.UnLock;
end;
if ((Service<>nil) and Service.ByPassAuthentication) or
((MethodIndex>=0) and Server.fPublishedMethod[MethodIndex].ByPassAuthentication) then
begin
result:=true;
exit;
end;
end
else
begin// default unique session if authentication is not enabled
Session := CONST_AUTHENTICATION_NOT_USED;
result := true;
end;
end;
with my test program **NOT** works, but you have to see if it does what it needs
Last edited by Sabbiolina (2015-04-22 22:01:22)
Offline
Please try http://synopse.info/fossil/info/e8335540d3
I refactored TSQLRestServerURIContext.Authenticate so that it should handle the authentication as expected.
Thanks for your feedback!
Offline
unfortunately it does not work.
I write a little cli/srv program, so you can debug.
ServerHB.pas
program ServerHB;
{$APPTYPE CONSOLE}
uses
SysUtils,
SynCommons, SynLog, mORMot,MormotHTTPserver,
mORMotSQLite3, SynSQLite3Static;
const
SERVER_PORT='888';
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function Add(n1,n2: integer): integer;
end;
Tmysess = class
function myerror(Sender: TSQLRestServer; Session: TAuthSession; Ctxt: TSQLRestServerURIContext): boolean;
end;
TMYAuthUser = class(TSQLAuthUser)
private
Fenabled:boolean;
published
property enabled: Boolean read Fenabled write Fenabled; // abilitato o no
function CanUserLog(Ctxt: TSQLRestServerURIContext): boolean; override;
end;
TServiceCalculator = class(TInterfacedObject, ICalculator)
public
function Add(n1,n2: integer): integer;
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;
function TMYAuthUser.CanUserLog(Ctxt: TSQLRestServerURIContext): boolean;
begin
result:=true;
end;
function Tmysess.myerror(Sender: TSQLRestServer; Session: TAuthSession; Ctxt: TSQLRestServerURIContext): boolean;
var
tmp:rawutf8;
begin
tmp:=ctxt.Call.InHead;
writeln('fail_inhead:'+tmp);
tmp:=ctxt.InHeader['Authorization'];
writeln('fail_auth:'+tmp);
result:=false;
end;
var
aModel: TSQLModel;
Restsrv:TSQLRestServerDB;
u:TMYAuthUser;
aHttpServer:TSQLHttpServer;
mySess:Tmysess;
begin
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator)]);
mysess:=Tmysess.Create;
aModel := TSQLModel.Create([TMYAuthUser,TSQLauthGroup]);
try
Restsrv:=TSQLRestServerDB.Create(aModel,':memory:',false);
try
restsrv.CreateMissingTables; // we need AuthGroup and AuthUser tables
restsrv.AuthenticationRegister(TSQLRestServerAuthenticationHttpBasic);
u:=TMYAuthUser.Create;
u.LogonName:='myuser';
u.DisplayName:='Test User';
u.PasswordPlain:='test';
u.enabled:=true;
u.GroupRights:=TSQLAuthGroup(3);
RestSrv.Add(U,true);
u.Free;
if not RestSrv.TableHasRows(TSQLAuthGroup) then
TSQLAuthGroup.InitializeTable(RestSrv,'',[itoNoIndex4ID, itoNoIndex4UniqueField, itoNoIndex4NestedRecord, itoNoIndex4RecordReference]);
restsrv.ServiceDefine(TServiceCalculator,[ICalculator],sicpersession);
RestSrv.OnSessionFailed:=mysess.myerror;
aHttpServer:=TSQLHttpServer.Create(SERVER_PORT,[RestSrv],'+',useHttpApiRegisteringURI);
aHttpServer.AccessControlAllowOrigin:='*'; // allow cross-site AJAX queries
write('Press [Enter] to close the server.');
readln;
finally
restsrv.Free;
end;
finally
aModel.Free;
end;
end.
ClientHB.pas
program ClientHB;
{$APPTYPE CONSOLE}
uses
SysUtils,
SynCommons, SynLog, mORMot,MormotHTTPserver,
mORMotSQLite3, SynSQLite3Static,mORMotHttpClient;
const
CLIENT_PORT='888';
CLIENT_ADDR='localhost';
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function Add(n1,n2: integer): integer;
end;
var
aModel:TSQLModel;
aClient:TSQLHttpClient;
I:ICalculator;
begin
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
// EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator)]);
aModel := TSQLModel.Create([TSQLAuthUser]);
aClient := TSQLHttpClientWinHTTP.Create(CLIENT_ADDR,CLIENT_PORT,aModel);
writeln('auth WRONG test:');
if TSQLRestServerAuthenticationHttpBasic.ClientSetUser(aClient,'myuser','test2',passClear) then
begin
writeln('auth OK');
aClient.ServiceDefine([ICalculator],sicpersession);
if aClient.Services['Calculator'].Get(I) then
writeln('result: '+ intToStr(I.Add(2,3)));
end
else Writeln('auth KO');
writeln('');
writeln('auth RIGHT test:');
if TSQLRestServerAuthenticationHttpBasic.ClientSetUser(aClient,'myuser','test',passClear) then
begin
writeln('auth OK');
aClient.ServiceDefine([ICalculator],sicpersession);
if aClient.Services['Calculator'].Get(I) then
writeln('result: '+ intToStr(I.Add(2,3)));
end
else Writeln('auth KO');
write('Press [Enter] to close the client.');
readln;
end.
Offline
Thanks to your programs, I found out what was not as you expected.
I've ensured that TSQLRestServer.OnSessionFailed event is called when the incoming credential (e.g. username or password) are incorrect.
Previous implementation only triggered the event when the URI signature was invalid.
Warning: any overriden TSQLRestServerURIContext.AuthenticationFailed method should call the inherited method to ensure OnSessionFailed is called.
Offline
we are almost there ...
but, for the http basic need a small patch
function TSQLRestServerAuthenticationHttpBasic.Auth(Ctxt: TSQLRestServerURIContext): boolean;
var userPass,user,pass,expectedPass: RawUTF8;
U: TSQLAuthUser;
Session: TAuthSession;
begin
if Ctxt.InputExists['UserName'] then begin
result := false; // allow other schemes to check this request
exit;
end;
result := true; // this authentication method is exclusive to any other
if GetUserPassFromInHead(Ctxt,userPass,user,pass) then begin
U := GetUser(Ctxt,user);
if U<>nil then begin
try
expectedPass := U.PasswordHashHexa;
U.PasswordPlain := pass; // override with SHA-256 hash from HTTP header
if U.PasswordHashHexa=expectedPass then begin
fServer.SessionCreate(U,Ctxt,Session);
if Session<>nil then begin
// see TSQLRestServerAuthenticationHttpAbstract.ClientSessionSign()
Ctxt.SetOutSetCookie((COOKIE_SESSION+'=')+CardinalToHex(Session.IDCardinal));
Ctxt.Returns(['result',Session.IDCardinal,'logonname',Session.User.LogonName]);
exit; // success
end;
end;
finally U.Free; end
end;
Ctxt.AuthenticationFailed; //<--------------------------- moved
exit;
end;
Ctxt.Call.OutHead := 'WWW-Authenticate: Basic realm="mORMot Server"';;
Ctxt.Error('',HTML_UNAUTHORIZED); // will popup for credentials in browser
end;
I moved the call Ctxt.AuthenticationFailed,
because in the case of existing user with the wrong password, the sesionfail was not called
Offline
Please check http://synopse.info/fossil/info/a067d1529e
Thanks for the feedback.
Offline
Another update.
We have introduced a breaking change, to add some feature to the authentication failure callback.
We renamed the TSQLRestServer.OnSessionFailed event as TSQLRestServer.OnAuthenticationFailed, including a new TNotifyAuthenticationFailedReason parameter so that the callback would be able to identify which kind of failure did occur.
See http://synopse.info/fossil/info/59a04453f1
Offline
I received this email from you:
lease check the changes Sabbiolina and I have been making to mORMot.pas
We have added an event handler to create a callback to use our own password verification scheme.
At the moment we ONLY modified the HTTP BASIC authentication class, but if you like our solution I’m sure you can easily “incorporate and port” it to all other auth types.
I think that freeing users from having to stick to the built-in auth scheme and classes would be a great plus for mORMot!
Good ideas.
About the password check, you can already override and TSQLRestServerAuthentication class, and provide your own customized password validation, or - even better perhaps, for your particular case - create you own TSQLAuthUser class and change how password is stored.
This is IMHO a better approach that shared events, which are highly depending on the authentication class itself (e.g. it does not make sense to have such an event in SSPI authentication mode).
About the user retrieval, you can already override the TSQLRestServerAuthentication.GetUser virtual method.
But perhaps it is not enough, since getting the user is common to all TSQLRestServerAuthentication classes.
So I've introduced the TSQLRestServer.OnAuthenticationUserRetrieve optional event, which would be used by TSQLRestServerAuthentication.GetUser to retrieve the TSQLAuthUser instance from a given name.
See http://synopse.info/fossil/info/9c496920bb
Thanks for the feedback.
Offline
Hello!
Thank you... but... deriving from your base (user) class would be possible if it was a new software, where you don't have to deal with legacy classes.
Not to mention that deriving would still require the modification of mORMot.pas, given the amount of protected members you use.
But when you are integrating mORMot into an existing 8-year-old code, you need enough flexibility to use your old classes (TMyUserProfile) as changing them would require way too much work to justify the effort.
So the changes that Sabbiolina and I made were thought/designed to give mORMot this kind of flexibility.
Wouldn't you agree?
Offline
Everything can be improved
I think it's the spirit of open source.
Leaving aside the usefulness or not to rewrite a new class of authentication httpbasic, I think the CheckCredential, as has been thought, is more efficient function that I have proposed and implemented long ago: CanUserLog.
The checkCredential, without changing a single line of mormot.pas, allows a developer to have full control, if he likes it, the authentication process could easily integrate with any variables derived from object tsqlauthuser.
While canUserLog, acting perhaps a little late in the authentication process, removing the ability to implement custom rules.
Offline
Indeed.
But I would not add a global TSQLRestServer.OnCheckPassword event, since it would not make sense for any supported authentication scheme, but when the password is transmitted as plain information.
I've therefore enhanced HTTP Basic authentication (which transmit the password from client to server side), to allow any kind of customization:
* added TSQLRestServerAuthenticationHttpBasic.CheckPassword() virtual method, so that you may customize the password check by overriding it
* defined GetUserPassFromInHead method as virtual, so that you could implement you own password transmission pattern, if the default awfully weak "Authorization: Basic ...." header does not fit your expectations
See http://synopse.info/fossil/info/835ea10b2f
Hope it helps.
Thanks for your feedback.
Offline
Tomorrow I'll try to improve the function checkpassword to meet the needs of existing authentication systems.
The endeavor to create a new class that inherits from authattpbasic just for that single detail (which by the way would be useful to everybody and would not break compliance with the HTTP Basic Auth standard) just seems to me like an unjustified waste of time...
Offline
A few more lines of code, perhaps.
But a much better design, and the opportunity to avoid potential misuses.
A global event would clearly break the liskov substitution principle I'm afraid...
Offline
What's the matter?
have a simple checkpassword violates the principles?
canUserLog instead?
Offline
I gave the reason just above:
I would not add a global TSQLRestServer.OnCheckPassword event, since it would not make sense for any supported authentication scheme, but when the password is transmitted as plain information.
A global event, at TSQLRestServer level, should have the same effect, whatever the TSQLRestServerAuthentication class is.
This is tied to the Liskov principle: a behavior should be explicit, whatever actual implementation is.
For TSQLAuthUser.CanUserLog, this event will be used whatever the TSQLRestServerAuthentication class is.
Whereas a TSQLAuthUser.CheckPassword, with a plain password, would only be called for TSQLRestServerAuthenticationHttpBasic. This would be a cause of confusion, for sure.
Offline
I looked at the use of passwords in mormot.pas
As you say, it is difficult to unify control of password authentication for all systems.
Especially for the use of passwordhexA as SALT communication.
Let me say that your constant 'salt' used between client and server is cryptographically poor, and hides a big problem in my opinion:
if you had a real cryptographic salt for each user how to connect a client to the server?
the client should already know the SALT.
Indeed passwordhexA = sha256 ('salt' + password);
perhaps think of a signature property in user with a separate management of the password might be interesting.
Offline
I'm afraid you are missing some points of the authentication process.
A fixed salt is not cryptographically poor, for the authentication scheme itself.
Its purpose is to avoid brute force resolution of the hashed password, if the server side database has been hacked.
In fact, the safety comes from the strong cryptography of SHA-256 itself.
Some standard does not even use a fixed salt.
The idea is that the dual-phase challenge is where the safety comes from.
In the dual-phase challenge, the salt changes at each request.
So it is even much more stronger than another per-user or per-application salt, as you propose.
This is why the BASIC authentication is weak, and should never be used.
As soon as you use TSQLRestServerAuthenticationHttpBasic, your authentication is weak.
Changing the way it is stored on the server would not help.
Offline
Pardon me for the intrusion, I used to teach Operating Systems Security at the University back in my country of origin, and I agree with Sabbiolina on the fact that a fixed salt is a pretty weak solution.
In fact your authentication scheme is so peculiar that it's pretty easy to "guesstimate" the REST service was built with mORMot, and your library is open source, therefore it would take seconds to an attacker to download your source code and realize the salt is constant. Using such information it would be fairly easy to bruteforce the back-end DB directly.
On top of that: a constant salt and no salt at all is basically the same thing from a cryptoanalytical strandpoint, as 2 salted SHA256's of the same password will result in the exact same has code:
- SHA256 of "saltMyPassword" is identical to SHA256 of "saltMyPassword", whereas
- SHA256 of "1234MyPassword" is not the same as SHA256 of "5678MyPassword"
In the latter case, in fact, because of the 2 different salts, the hash codes of the same password would be different, de facto preventing most linear and differential crypto-analysis attacks.
Offline
Changing the way it is stored on the server would not help.
Again, since your library is a framework, it is not good to assume all your users are developing NEW software (and thus have the ability to structure the back-end DB as you suggest).
When you have to integrate new parts in a very large existing software, sometimes you have to "make do".
So, let's say that we're dealing with an existing DB, where salts and password-hashes are already stored that way, in separate fields... I do believe Sabbiolina's solution here would make your library flexible enough to handle such case with a minimal amount of lines of code (as opposed to reinventing the wheel, and subclassing a class that can only be subclassed inside mORMot.pas due to the large use of protected members). Wouldn't you agree?
Last edited by BBackSoon (2015-04-29 16:58:35)
Offline
@BBackSoon
This was exactly what I stated: a fixed salt is weak, but if you store a variable salt within the User record, or use a custom application-dedicated salt, it would not make a big difference.
I disagree a little about the fact that "a constant salt and no salt at all is basically the same thing".
From a real attacker point of view a salt, even fixed, is better than no salt, since you can't use brute-force pre-computed dictionnary.
With a custom salt, you need to compute all SHA, whereas with no salt, you can use pre-existing pre-computed dictionary, which are in every attacker tool box.
And you can store the TSQLAuthUser+TSQLAuthGroup tables in a crypted SQlite3 database, difficult to decrypt without the private key...
If the attacker is able to access the back-end DB, I would not fear about breaking the password in brute force.
There will be much worse to be done if you access the back-end computer!
Once again, the main point of the "default" mORMot security scheme is the dual-phase challenge at the initialization phase, then signature of every request, at URI level, with a time-based nonce.
This is were the security relies.
The password, nor its hash with a fixed salt, is never transmitted on the wire.
The password storage is IMHO strong enough, and you have the ability to change how it is stored easily by sub-classing the TSQLAuthUser if needed.
Sabbiolina is using basic authentication, with the password transmitted in clear!
A custom password check on the server is just a no benefit, when using such a weak authentication.
I do not see what is wrong with subclass a mORMot class.
You can inject any external existing code in the subclass.
There is in fact no new DB needed: you can create a TSQLAuthUser on the fly from an existing DB, and it would work.
Whereas putting a shared event on the main TSQLRestServer would IMHO just introduce confusion, since we support several authentication schemes.
Such an event would imply that the password is send as plain from the client side, which is the worse and weaker design we could imagine.
This is why I did not want to go into this direction.
Offline
I know that the HTTP Basic authentication is inherently weak, in fact Sabbiolina is forcing it through a TLS channel. Yet it still remains fairly weak for other reasons.
But it's compatible. If mORMot had supported a more secure - yet highly compatible - authentication scheme, like OAuth(2) for instance, we would have gone that way.
mORMot's native auth scheme, with its two-phase challenge, is strong, but it's a pain if you need to create an API to be accessed by apps written in PHP, Python, Ruby, Java, C# (etc...) because no one wants to implement the client-side of mORMot's auth scheme when the languages they use already have very reliable and mature OAuth client libraries they could leverage upon.
On top of that consider when you have zero control over the backend DB, and you have to abide to restrictions (no SQLite for example) imposed by either the software or the politics behind it... then you need a framework flexible enough to let you go around these hurdles.
Summarizing: OAuth(2) is really a must if we don't want to pretend that every developer in the world uses Delphi/mORMot... in the meantime - waiting for OAuth(2) - a HTTP Basic Auth inside a TLS channel with some more flexibility would be good enough (temporarily).
Offline
About HTTP Basic auth, even over TLS it is weak, since it is subject to MIM attacks if certificates are not fully trusted...
Yes OAuth2 is a need, since it is standard.
But its implementations could be found very weak - weaker than the mORMot default authentication, in all means.
For instance, reading of http://tools.ietf.org/html/rfc6749 is informative:
- The password is sent as plain text, e.g. via HTTP basic authentication;
- The Access Token is sent as HTTP header, regardless of the resource accessed, and with cross-origin abilities;
- Due to all those weaknesses, TLS is needed... even if it is no silver bullet either.
One of the initial authors quit the group and withdrew its name from the RFC... see http://hueniverse.com/2012/07/26/oauth- … d-to-hell/
OAuth2 is pretty sensitive to MIM and Replay attacks, and has a lot of well identified threats.
This is well documented - see https://tools.ietf.org/html/rfc6819
There is a pending port for mORMot - see http://synopse.info/forum/viewtopic.php … 780#p15780
Any help introducing it in the framework is welcome.
The scheme used by mORMot, with a dual-step challenge and a nonce-based URI/resource level signature, is somewhat stronger.
You may consider even using mORMot without HTTPS.
I do not understand what is wrong with Sqlite, which is the most widely deployed database, and probably the most tested and safest db in the world.
But even SQlite3 is not mandatory in mORMot.
As I wrote, you can store the users in any kind of storage, just create the TSQLAuthUser instance on the fly.
We have Java and JavaScript clients available.
Others submissions, using mustache templates are welcome.
Offline