Kaseya Community

The new REST API in VSA R9.3

  • I will respond to Dana's post off-forum as this isn't really a public matter.

    In any case, there was no mention of swagger being available (as far as I can see) until Dana told us a few posts ago... I wonder how we're supposed to know about features that are completely undocumented.

    In any case, I can achieve AUTH using swagger also, however it *still* doesn't explain how to correctly make a third party request. e.g. the description of the password field is "SHA265 hash of password using challenge value" - still not sure how to generate the "challenge value". have you had any luck working this out??

    Also, when i do get back a successful auth, it looks like this:

    {

     "Result": {

       "TenantId": "1",

       "Token": "7605xxxx",

       "UserName": "Craig Hart",

       "RoleId": 2,

       "ScopeId": "2",

       "AdminId": 64975473,

       "MachineId": null,

       "MachineGroupName": null,

       "Attributes": null

     },

     "ResponseCode": 0,

     "Status": "OK",

     "Error": "None"

    }

    So, my token for further requests is 7605xxxx, correct?

    So then i go down to GET GET /assetmgmt/agents which accepts one parameter - the token - which I paste in, and the result I get is as follows:

    {

     "ResponseCode": 401,

     "Status": "OK",

     "Error": "None"

    }

    I was expecting a list of agents to be returned. Not sure what i'm doing wrong....?

  • you have to type "Bearer" in the Authorization value field as well

    e.g. your token for further requests is: "Bearer 7605xxxx"  

    The "challenge value" is a random generated number.

    In "http://<yourkaseyaserver>/api/v1.0/swagger/ui/ext/Kaseya-API-Core-Swagger-SwaggerUITweaks-js" you can see how the Authorization is made. Looks like SHA256 is being used multiple times in multiple combinations.

    For now I just use the token that's generated by swagger but I do not know how long it takes before the token expires. Maybe Dana could tell us.

  • Bingo. 'Bearer xxxxxxxx' works. Thank you :)

    I get a different token each time I auth via swagger (as is to be expected) - it seems so far in our testing that the token is valid until a fresh one is issued (i.e. fresh AUTH request is made) but i'm sure there must be a timeout somewhere.

    The authorization routine itself is rather complex:

    first, the user supplies the username & password & twofapass as the plaintext.

          var random = Math.floor(Math.random() * 1000000);

    next, generate a random number (presumably to use as a salt)

          var hashes = getHashes(username, password, random, '');

    Calculate various hashes of the username password + the random number (used as a salt, apparently)

          var header = 'user=' + username + ',pass2=' + hashes.SHA256Hash + ',pass1=' + hashes.SHA1Hash + ',rand2=' + random + ',rpass2=' + hashes.RawSHA256Hash + ',rpass1=' + hashes.RawSHA1Hash + ',twofapass=' + twofapass;

    Supply the username and salt and 2fa in plaintext + their calculated values.

    So the header is:

    user= the plaintext username

    pass2= the SHA256 hash of (name+pass+random)

    pass1= the SHA1 hash of (name+pass+random)

    rand2= the plaintext random number

    rpass2= the 'raw' SHA256 hash of (name+pass+random)

    rpass1= the 'raw' SHA1 hash of (name+pass+random)

    twofapass= the plaintext twofapass

    Interesting logic indeed. Is this supposed to be some super-secure system, in that it uses both SHA1 and SHA256 + the random as a salt, in order to defeat brute-forcing/rainbow table type attacks??

    In actual fact since the username, salt and 2fa are all transmitted in the clear, only the password remains unknown..

    i'm no expert in hashing and password security, but it doesn't look like very good security to me to be freely supplying the salt in plaintext, as that effectively eliminates the purpose of a salt in the first place and should make breaking the encryption fairly trivial.

  • Thanks for that post Craig. I'm anxious to upgrade to get a look at the API. I think this info will save quite a bit of time figuring it out.

    As far as sending the the salt in plain text, my opinion is your going to be okay. A few things to consider:

    • First, everyone should be using HTTPS in production. Overhead is low and certs are cheap. So it shouldn't be completely plain text.
    • When you log into the web interface on any website, you also send your password in plain text, so this is at least not human readable. You could hash client side with javascript, but then the same salt is used everywhere and your algorithm is public.
    • If you store the salt on the server side, you have to send a plain text password and let the server hash it, which isn't bad, just definitely not more secure.
    • If the traffic with the salt is sniffed, the token will be sniffed just as easily.
    • Just the simple act of salting, even if the salt is known, prevents a pre-computed hash lookup.
    • The only purpose of the salt really is to prevent dictionary attacks, so complex passwords without salt are equal to any password with salt.

    Even knowing the salt, cracking a SHA1 hash is far from trivial as long as a complex password is used so a dictionary attack can't be used. Last I read I think it was 9 months using a machine that costs in the $100k range. SHA256 is near impossible.

    Are there more secure ways to do this? Yes. Not use a password auth at all. In today's world though, password authentication is considered acceptable and there just is not a perfect way to handle passing a password.

    Disclaimer, I'm no expert either. Just a conversation I've had many times with other developers to ensure I'm building things the best way.

  • I just wanted to reference this excellent article by Troy Hunt, who in 2012 demonstrated using a consumer graphics card to successfully crack real world salted SHA1 passwords in a very short time.  The TL;DR - "salted SHA hashes are near useless against the bulk of passwords users typically create."

    Although I concur with jondle, salts themselves don't need to be secrets.

    www.troyhunt.com/our-password-hashing-has-no-clothes

  • Anyone have any luck with the Auth endpoint?

    Whatever I try either returns "Auth header is missing user parameter" (even though it's there) or ResponseCode 4000002 with no error.

  • Dear Ivanrkv,

    If you go to "http://<yourkaseyaserver>/api/v1.0/swagger" you can authorize using your Kaseya username and password. You will get in return a token, use this token in your Authorization header as "Bearer [token]".

    The token is set up using your Kaseya username, password and a random generated number as you can see on "http://<yourkaseyaserver>/api/v1.0/swagger/ui/ext/Kaseya-API-Core-Swagger-SwaggerUITweaks-js".

    Regards,

    Pattie

  • So is the idea that you request a bearer/token that lives forever? That seems strange since the token-based systems that I know of have an expiration time for the token and you request a new token for each transaction. Furthermore, every time you generate a new token you would be increasing your chances of getting hacked via brute force.

    Anyway, I spent some time this morning working with this and now have a breakdown of how to request a token from auth outside of Swagger. I'll do it by example, and hopefully this is clear. Please note, I am not dealing with a domain account. Domain accounts are handled differently and I'll try to call out how those *should* work, but I haven't tested. I am also not dealing with 2-factor authentication -- I have no idea how that works in this context.

    STEP 1 - Get a username, password and random number:

    • Note that leading and trailing spaces on all of these will be removed
    • If you are using a username with a domain name (i.e. DOMAIN\user, DOMAIN/user, or user@domain.com) you need to have 2 different username formats -- the one you use for login (e.g. user@domain.com) and the one that is calculated by the script -- this is in the format [domain]/[user], so in this example, it would be domain.com/user.

    Random: 998877
    User: testuser
    Pass: SomethingMoreSecureThanThis
    twofapass=:undefined

    STEP 2 - Hash Pass+User with SH256 and SHA1:

    • Concatenate Password+User = SomethingMoreSecureThanThistestuser
    • Repeat, if necessary with the modified username (e.g. SomethingMoreSecureThanThisdomain.com/user)
    • Hash the above as both SHA1 and SHA256 and store it. Since I'm doing a non-domain account, I have only 2 hashes. If you have a domain account, you'll have 4.

    SHA1 Hash = 84d015ab5dad524a428ddbd1d96b27e35bef07b9
    SHA256 Hash = 9968fbf6aa432a498e1cc159f5a0cfe0501ba2bc14e51a29bead555194595c38

    STEP 3 - Hash prior hash+random number with SHA256 and SHA1:

    • Concatenate each hash calculated in STEP 2 with the random number added to the end. Again, if you're using a domain account, you'll have 4 hashes, not 2.

    Value = 84d015ab5dad524a428ddbd1d96b27e35bef07b9998877
    SHA1 Hash = a94e5f7c12d1599e538cffb61bc15ab8c7ab7ced

    Value = 9968fbf6aa432a498e1cc159f5a0cfe0501ba2bc14e51a29bead555194595c38998877
    SHA256 Hash = 687e5a021386639f86ba48aecfadbc162de3f28128e6e6772807d80cdf38615b

    STEP 4 - Build the authorization string:

    • The authorization string contains the following fields in this order, concatenated with commas as separators
      • user - plain text username
      • pass2 - SHA256 hash calculated in STEP 3 (if you're using a domain account, this would be the one using the calculated username)
      • pass1 - SHA1 hash calculated in STEP 3 (if you're using a domain account, this would be the one using the calculated username)
      • rand2 - plain text random number
      • rpass2 - SHA256 hash calculated in STEP 3 (if you're using a domain account, this would be the one using the raw username before modification)
      • rpass1 - - SHA1 hash calculated in STEP 3 (if you're using a domain account, this would be the one using the raw username before modification)
      • twofapass - Since I don't know how this works, I'm just using ":undefined"

    So my authorization string looks like this:
    user=testuser,pass2=687e5a021386639f86ba48aecfadbc162de3f28128e6e6772807d80cdf38615b,pass1=a94e5f7c12d1599e538cffb61bc15ab8c7ab7ced,rand2=998877,rpass2=687e5a021386639f86ba48aecfadbc162de3f28128e6e6772807d80cdf38615b,rpass1=a94e5f7c12d1599e538cffb61bc15ab8c7ab7ced,twofapass=:undefined

    STEP 5 - Encode the authorization string with Base64:

    Doing this by hand, I just used the command line, like this:
    echo -n user=testuser,pass2=687e5a021386639f86ba48aecfadbc162de3f28128e6e6772807d80cdf38615b,pass1=a94e5f7c12d1599e538cffb61bc15ab8c7ab7ced,rand2=998877,rpass2=687e5a021386639f86ba48aecfadbc162de3f28128e6e6772807d80cdf38615b,rpass1=a94e5f7c12d1599e538cffb61bc15ab8c7ab7ced,twofapass=:undefined|base64 -w 0

    Which gave me this Base64 encoded string:
    dXNlcj10ZXN0dXNlcixwYXNzMj02ODdlNWEwMjEzODY2MzlmODZiYTQ4YWVjZmFkYmMxNjJkZTNmMjgxMjhlNmU2NzcyODA3ZDgwY2RmMzg2MTViLHBhc3MxPWE5NGU1ZjdjMTJkMTU5OWU1MzhjZmZiNjFiYzE1YWI4YzdhYjdjZWQscmFuZDI9OTk4ODc3LHJwYXNzMj02ODdlNWEwMjEzODY2MzlmODZiYTQ4YWVjZmFkYmMxNjJkZTNmMjgxMjhlNmU2NzcyODA3ZDgwY2RmMzg2MTViLHJwYXNzMT1hOTRlNWY3YzEyZDE1OTllNTM4Y2ZmYjYxYmMxNWFiOGM3YWI3Y2VkLHR3b2ZhcGFzcz06dW5kZWZpbmVk

    STEP 6 - HTTP GET with basic authorization header:

    Again, doing this by hand, I used CURL, like this:
    curl -H 'Authorization: Basic dXNlcj10ZXN0dXNlcixwYXNzMj02ODdlNWEwMjEzODY2MzlmODZiYTQ4YWVjZmFkYmMxNjJkZTNmMjgxMjhlNmU2NzcyODA3ZDgwY2RmMzg2MTViLHBhc3MxPWE5NGU1ZjdjMTJkMTU5OWU1MzhjZmZiNjFiYzE1YWI4YzdhYjdjZWQscmFuZDI9OTk4ODc3LHJwYXNzMj02ODdlNWEwMjEzODY2MzlmODZiYTQ4YWVjZmFkYmMxNjJkZTNmMjgxMjhlNmU2NzcyODA3ZDgwY2RmMzg2MTViLHJwYXNzMT1hOTRlNWY3YzEyZDE1OTllNTM4Y2ZmYjYxYmMxNWFiOGM3YWI3Y2VkLHR3b2ZhcGFzcz06dW5kZWZpbmVk' --url https://YOUR_VSA_URL_HERE/api/v1.0/auth

    In my case, CURL returned the following (TokenId and AdminId scrubbed for safety):
      "Result": {
        "TenantId": "1",
        "Token": "00000000",
        "UserName": "testuser",
        "RoleId": 2,
        "ScopeId": "2",
        "AdminId": 00000000,
        "MachineId": null,
        "MachineGroupName": null,
        "Attributes": null
      },
      "ResponseCode": 0,
      "Status": "OK",
      "Error": "None"
    }

    I hope this helps someone else out.

    Note: RawCap (http://www.netresec.com/?page=RawCap) was a huge help in seeing the actual HTTP packets on the loopback interface of the server when doing the auth process with Swagger. It is really small, requires no installation, and it's output is easily parsed with Wireshark.

    Thanks to those of you above who provided details on how to find Swagger, the SwaggerUITweaks code, etc.


    Nate

  • Dear Nate,

    Thanks for sharing such a detailed explanation.

    My apologies, should have been more clear about this but I only use swagger for testing.

    I certainly didn't mean to imply that a token will last forever, from my experience a token will last for 1 hour, maybe 2.

  • Hey guys.

    So after some help from Kaseya support, I finally figured out connecting to the API.

    From the link someone mentioned previously, http://<yourVSA>/api/v1.0/swagger/ui/ext/Kaseya-API-Core-Swagger-SwaggerUITweaks-js, you can grab all the hashing functions you need for authentication.

    The rest is your generic HTTPS request with basic authentication.

    var https = require('https');

    var auth = function(callback) {

     var username = 'username';

     var password = 'password';

     var random = Math.floor((Math.random() * 10000) + 1);

     var hashes = getHashes(username, password, random);

     var header = 'user=' + username + ',pass2=' + hashes.SHA256Hash + ',pass1=' + hashes.SHA1Hash + ',rand2=' + random + ',rpass2=' + hashes.RawSHA256Hash + ',rpass1=' + hashes.RawSHA1Hash;

     var url = 'yoururl.here'

     var options = {

       host: url,

       path: '/api/v1.0/auth',

       method: 'GET',

       headers: {'Authorization': 'Basic ' + new Buffer(header).toString('base64')}

     }

     var req = https.request(options, function(res) {

       var body = '';

       res.on('data', function(chunk) {

         body+=chunk

       });

       res.on('end', function() {

         callback(JSON.parse(body).Result.Token)

       })

       res.on('error', function(e) {

         console.log(e)

       })

     })

     req.end();

    }

    getHashes function is in the URL linked.

    Header hashes are an interesting choice, but whatever.

    The token returned is the bearer token you can use for all the other calls.

    Hope this helps.



    Removed rejectUnauthorized flag. Should be done over secure connection.
    [edited by: ivanrkv at 10:16 AM (GMT -7) on Jun 21, 2016]
  • I've tried every single combination I can think of using the methodology you're suggesting here but I keep getting the same response of "Missing Authorization Header".  I'm sending along the username and password header values as suggested in the documentation and in this post.  If I try to add the authorization header using my login credentials as I do with every other REST API I've ever hit, I get a status code of 4000002 with no error message.

    Can you please provide an sample of the headers required for the /auth request?  Here is what I've gathered from you based on this post.

    username: <vsa admin username>

    password: <sha256 hashed vsa admin password>

    authorization: ? (what is the format for the authorization header? basic with the base64 user:pass combo doesnt work, basic with a sha256 hased user:pass combo does not work either.)

  • accutech, if you look at my post above, i've outlined the header format.

    var header = 'user=' + username + ',pass2=' + hashes.SHA256Hash + ',pass1=' + hashes.SHA1Hash + ',rand2=' + random + ',rpass2=' + hashes.RawSHA256Hash + ',rpass1=' + hashes.RawSHA1Hash;

    Then the actual header looks like this :  {'Authorization': 'Basic ' + new Buffer(header).toString('base64')}

  • How are you generating your password hash? ivanrkv is correct that the header is basically:

    Authorization: Basic <Base64_Encoded_Authentication_String>

  • This did the trick...I followed this example and got the token and got the response I was looking for.  It's just a shame that this information isn't present in the documentation.  Thanks for the detailed reply Nate!

  • I managed to decypher the old API (SOAP) using the example webpages provided but i can't find anything similar using rest to auth against VSA.

    Could anyone please post an example of how a PHP-page with curl would look like, authing against VSA in rest?

    Cheers!



    edit
    [edited by: Erik Ribbhammar at 5:40 AM (GMT -7) on Oct 6, 2016]