How to secure REST API from replay attacks with parameter manipulation? - rest

I am developing secure payment APIs, and I want to avoid replay attacks with manipulation of the parameters in the url. For example in the following API call:
https://api.payment.com/wallet/transfer?from_account=123&to_account=456&amount=100
Once this API call is executed, someone with enough knowledge can execute the same API call by modifying any of the three parameters to his/her own advantage. I have thought of issuing a temporary token (transaction token) for each transaction. But this also doesn't sounds like enough.
Can anyone suggest the best way to mitigate replay attacks with parameters tampering?

THE API SERVER
I am developing secure payment APIs, and I want to avoid replay attacks with manipulation of the parameters in the url.
Before we dive into addressing your concerns it's important to first clarify a common misconception among developers, that relates to knowing the difference between who vs what is accessing the API server.
The difference between who and what is accessing the API server.
This is discussed in more detail in this article I wrote, where we can read:
The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is it a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?
The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.
If the quoted text is not enough for you to understand the differences, then please go ahead and read the entire section of the article, because without this being well understood you are prone to apply less effective security measures in your API server and clients.
SECURITY LAYERS AND PARAMETERS IN THE URL
For example in the following API call:
https://api.payment.com/wallet/transfer?from_account=123&to_account=456&amount=100
Security is all about applying as many layers of defence as possible in order to make the attack as harder and laborious as possible, think of it as the many layers in an onion you need to peel to arrive to the center one.
Attackers will always look for the most easy targets, the lower hanging fruit in the tree, because they don't want to resort to use a ladder when they can take the fruit from another tree with lower hanging fruit ;)
So one of the first layers of defense is to avoid using parameters in the url for sensitive calls, thus I would use a POST request with all the parameters in the body of the request, because this type of request cannot be done by simply copy paste the url into the browser or any other tool, thus they require more effort and knowledge to be performed, aka the fruit is more high in the tree for the attacker.
Another reason is that GET requests end up in the logs of the servers, thus can be accidentally exposed and easily replayed.
REPLAY ATTACKS FOR API CALLS
Once this API call is executed, someone with enough knowledge can execute the same API call by modifying any of the three parameters to his/her own advantage.
Yes they can, and they can learn how your API works even if you don't have public documentation for it, they just need to reveres engineer it with the help of any open source tool for mobile apps and web apps.
I have thought of issuing a temporary token (transaction token) for each transaction. But this also doesn't sounds like enough.
Yes it's not enough because this temporary token can be stolen via a MitM attack, just like a show in the article Steal That Api Key With a Man in the Middle Attack:
So, in this article you will learn how to setup and run a MitM attack to intercept https traffic in a mobile device under your control, so that you can steal the API key. Finally, you will see at a high level how MitM attacks can be mitigated.
So after performing the MitM attack to steal the token it's easy to use curl, Postman or any other similar tool to make the requests to the API server just like if you are the genuine who and what the API server expects.
MITIGATE REPLAY ATTACKS
Improving on Existing Security Defence
I have thought of issuing a temporary token (transaction token) for each transaction. But this also doesn't sounds like enough.
This approach is good but not enough as you alreay noticed, but you can improve it, if not have done it already, by making this temporary token usable only one time.
Another important defence measure is to not allow the requests with same amount and same recipients(from_account, to_account) be repeated in sequence, even if they have a new temporary token.
Also don't allow requests from the same source to be made to fast, specially if they are intended to come from human interactions.
This measures on their own will not totally solve the issue, but add some more layers into the onion.
Using HMAC for the One Time Token
In order to try to help the server to be confident about who and what is making the request you can use a Keyed-Hash Message Authentication Code (HMAC) which is designed to prevent hijacking and tampering, and as per Wikipedia:
In cryptography, an HMAC (sometimes expanded as either keyed-hash message authentication code or hash-based message authentication code) is a specific type of message authentication code (MAC) involving a cryptographic hash function and a secret cryptographic key. As with any MAC, it may be used to simultaneously verify both the data integrity and the authenticity of a message.
So you could have the client creating an HMAC token with the request url, user authentication token, your temporary token, and the time stamp that should be also present in a request header. The server would then grab the same data from the request and perform it's own calculation of the HMAC token, and only proceed with the request if it's own result matches the one for the HMAC token header in the request.
For a practical example of this in action you can read part 1 and part 2 of this blog series about API protection techniques in the context of a mobile app, that also features a web app impersonating the mobile app.
So you can see here how the mobile app calculates the HMAC, and here how the Api server calculates and validates it. But you can also see here how the web app fakes the HMAC token to make the API server think that the requests is indeed from who and what it expects to come from, the mobile app.
Mobile App Code::
/**
* Compute an API request HMAC using the given request URL and authorization request header value.
*
* #param context the application context
* #param url the request URL
* #param authHeaderValue the value of the authorization request header
* #return the request HMAC
*/
private fun calculateAPIRequestHMAC(url: URL, authHeaderValue: String): String {
val secret = HMAC_SECRET
var keySpec: SecretKeySpec
// Configure the request HMAC based on the demo stage
when (currentDemoStage) {
DemoStage.API_KEY_PROTECTION, DemoStage.APPROOV_APP_AUTH_PROTECTION -> {
throw IllegalStateException("calculateAPIRequestHMAC() not used in this demo stage")
}
DemoStage.HMAC_STATIC_SECRET_PROTECTION -> {
// Just use the static secret to initialise the key spec for this demo stage
keySpec = SecretKeySpec(Base64.decode(secret, Base64.DEFAULT), "HmacSHA256")
Log.i(TAG, "CALCULATE STATIC HMAC")
}
DemoStage.HMAC_DYNAMIC_SECRET_PROTECTION -> {
Log.i(TAG, "CALCULATE DYNAMIC HMAC")
// Obfuscate the static secret to produce a dynamic secret to initialise the key
// spec for this demo stage
val obfuscatedSecretData = Base64.decode(secret, Base64.DEFAULT)
val shipFastAPIKeyData = loadShipFastAPIKey().toByteArray(Charsets.UTF_8)
for (i in 0 until minOf(obfuscatedSecretData.size, shipFastAPIKeyData.size)) {
obfuscatedSecretData[i] = (obfuscatedSecretData[i].toInt() xor shipFastAPIKeyData[i].toInt()).toByte()
}
val obfuscatedSecret = Base64.encode(obfuscatedSecretData, Base64.DEFAULT)
keySpec = SecretKeySpec(Base64.decode(obfuscatedSecret, Base64.DEFAULT), "HmacSHA256")
}
}
Log.i(TAG, "protocol: ${url.protocol}")
Log.i(TAG, "host: ${url.host}")
Log.i(TAG, "path: ${url.path}")
Log.i(TAG, "Authentication: $authHeaderValue")
// Compute the request HMAC using the HMAC SHA-256 algorithm
val hmac = Mac.getInstance("HmacSHA256")
hmac.init(keySpec)
hmac.update(url.protocol.toByteArray(Charsets.UTF_8))
hmac.update(url.host.toByteArray(Charsets.UTF_8))
hmac.update(url.path.toByteArray(Charsets.UTF_8))
hmac.update(authHeaderValue.toByteArray(Charsets.UTF_8))
return hmac.doFinal().toHex()
}
API server code:
if (DEMO.CURRENT_STAGE == DEMO.STAGES.HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret during HMAC verification for this demo stage
hmac = crypto.createHmac('sha256', base64_decoded_hmac_secret)
log.info('---> VALIDATING STATIC HMAC <---')
} else if (DEMO.CURRENT_STAGE == DEMO.STAGES.HMAC_DYNAMIC_SECRET_PROTECTION) {
log.info('---> VALIDATING DYNAMIC HMAC <---')
// Obfuscate the static secret to produce a dynamic secret to use during HMAC
// verification for this demo stage
let obfuscatedSecretData = base64_decoded_hmac_secret
let shipFastAPIKeyData = new Buffer(config.SHIPFAST_API_KEY)
for (let i = 0; i < Math.min(obfuscatedSecretData.length, shipFastAPIKeyData.length); i++) {
obfuscatedSecretData[i] ^= shipFastAPIKeyData[i]
}
let obfuscatedSecret = new Buffer(obfuscatedSecretData).toString('base64')
hmac = crypto.createHmac('sha256', Buffer.from(obfuscatedSecret, 'base64'))
}
let requestProtocol
if (config.SHIPFAST_SERVER_BEHIND_PROXY) {
requestProtocol = req.get(config.SHIPFAST_REQUEST_PROXY_PROTOCOL_HEADER)
} else {
requestProtocol = req.protocol
}
log.info("protocol: " + requestProtocol)
log.info("host: " + req.hostname)
log.info("originalUrl: " + req.originalUrl)
log.info("Authorization: " + req.get('Authorization'))
// Compute the request HMAC using the HMAC SHA-256 algorithm
hmac.update(requestProtocol)
hmac.update(req.hostname)
hmac.update(req.originalUrl)
hmac.update(req.get('Authorization'))
let ourShipFastHMAC = hmac.digest('hex')
// Check to see if our HMAC matches the one sent in the request header
// and send an error response if it doesn't
if (ourShipFastHMAC != requestShipFastHMAC) {
log.error("\tShipFast HMAC invalid: received " + requestShipFastHMAC
+ " but should be " + ourShipFastHMAC)
res.status(403).send()
return
}
log.success("\nValid HMAC.")
Web APP code:
function computeHMAC(url, idToken) {
if (currentDemoStage == DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION
|| currentDemoStage == DEMO_STAGE.HMAC_DYNAMIC_SECRET_PROTECTION) {
var hmacSecret
if (currentDemoStage == DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret in the HMAC for this demo stage
hmacSecret = HMAC_SECRET
}
else if (currentDemoStage == DEMO_STAGE.HMAC_DYNAMIC_SECRET_PROTECTION) {
// Obfuscate the static secret to produce a dynamic secret to
// use in the HMAC for this demo stage
var staticSecret = HMAC_SECRET
var dynamicSecret = CryptoJS.enc.Base64.parse(staticSecret)
var shipFastAPIKey = CryptoJS.enc.Utf8.parse($("#shipfast-api-key-input").val())
for (var i = 0; i < Math.min(dynamicSecret.words.length, shipFastAPIKey.words.length); i++) {
dynamicSecret.words[i] ^= shipFastAPIKey.words[i]
}
dynamicSecret = CryptoJS.enc.Base64.stringify(dynamicSecret)
hmacSecret = dynamicSecret
}
if (hmacSecret) {
var parser = document.createElement('a')
parser.href = url
var msg = parser.protocol.substring(0, parser.protocol.length - 1)
+ parser.hostname + parser.pathname + idToken
var hmac = CryptoJS.HmacSHA256(msg, CryptoJS.enc.Base64.parse(hmacSecret)).toString(CryptoJS.enc.Hex)
return hmac
}
}
return null
}
NOTE: While the above code is not using the exact same parameters that you would use in your case, it is a good starting pointing for you to understand the basics of it.
As you can see the way the HMAC token is calculated across mobile app, Api server and the Web app are identical in the semantics of the logic, thus resulting in the same HMAC token, and this way the Web app is able to defeat the Api server defense to only accept valid request from the mobile app.
The bottom line here is that anything you place in the client code can be reverse engineered in order to replicate it in another client. So should I use HMAC tokens in my use case?
Yes, because it's one more layer in the onion or a fruit more high in the tree.
Can I do better?
Yes you can do, just keep reading...
Enhance and Strength the Security
Can anyone suggest the best way to mitigate replay attacks with parameters tampering?
Going with the layered defence approach once more, you should look to other layered approaches that will allow your API server to be more confident about who and waht is accessing it.
So if the clients of you API server are only mobile apps, then please read this answer for the question How to secure an API REST for mobile app?.
In the case you need to secure an API that serves both a mobile and web app, then see this another answer for the question Unauthorized API Calls - Secure and allow only registered Frontend app.
GOING THE EXTRA MILE
Now I would like to recommend you the excellent work of the OWASP foundation:
The Web Security Testing Guide:
The OWASP Web Security Testing Guide includes a "best practice" penetration testing framework which users can implement in their own organizations and a "low level" penetration testing guide that describes techniques for testing most common web application and web service security issues.
The Mobile Security Testing Guide:
The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security development, testing and reverse engineering.

Related

IdentityServer3 idsrv.partial cookie gets too big

After login when redirecting the user using context.AuthenticateResult = new AuthenticateResult(<destination>, subject, name, claims) the partial cookie gets so big that it contains up to 4 chunks and ends up causing "request too big" error.
The number of claims is not outrageous (in the 100 range) and I haven't been able to consistently reproduce this on other environments, even with larger number of claims. What else might be affecting the size of this cookie payload?
Running IdSrv3 2.6.1
I assume that you are using some .NET Framework clients, because all of these problems are usually connected with the Microsoft.Owin middleware, that has some encryption that causes the cookie to get this big.
The solution for you is again part of this middleware. All of your clients (using the Identity Server as authority) need to have a custom IAuthenticationSessionStore imlpementation.
This is an interface, part of Microsoft.Owin.Security.Cookies.
You need to implement it according to whatever store you want to use for it, but basically it has the following structure:
public interface IAuthenticationSessionStore
{
Task RemoveAsync(string key);
Task RenewAsync(string key, AuthenticationTicket ticket);
Task<AuthenticationTicket> RetrieveAsync(string key);
Task<string> StoreAsync(AuthenticationTicket ticket);
}
We ended up implementing a SQL Server store, for the cookies. Here is some example for Redis Implementation, and here is some other with EF DbContext, but don't feel forced to use any of those.
Lets say that you implement MyAuthenticationSessionStore : IAuthenticationSessionStore with all the values that it needs.
Then in your Owin Startup.cs when calling:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
SessionStore = new MyAuthenticationSessionStore()
CookieName = cookieName
});
By this, as the documentation for the IAuthenticationSessionStore SessionStore property says:
// An optional container in which to store the identity across requests. When used,
// only a session identifier is sent to the client. This can be used to mitigate
// potential problems with very large identities.
In your header you will have only the session identifier, and the identity itself, will be read from the Store that you have implemented

Firebase verifyIdToken + NodeJS Express Authentication design

Problem:
Due to legislation I have to store personal information within the EU (Social security number). Therefore I can't store this information in Firebase since there is no guarantee of geographical datacenter location when using Google's cloud services.
My proposed solution:
Having a Redis key value store with the sensitive information that can be accessed via a simple REST api where user authentication would be achieved using the users ID token, sent via HTTP Headers.
Firebase allows for verification of a user via the verifyIdToken method in the NodeJS library. This would allow me to check if the user ID matches any user id in my /admin end point of my Firebase. (Or I could hardcode the userIDs that would be allowed into the server since there aren't that many.)
So, the flow of the request would be as follows:
User signs in client side using the Firebase SDK.
Whenever the user needs access to the sensitive information it first gets the user's ID token
let currentUser = FIRAuth.auth()?.currentUser
currentUser?.getTokenForcingRefresh(true) {idToken, error in
if let error = error {
return
}
let headers = [
"X-FBUser-Token":idToken
]
//build request here to https://myServer.com/myEndpoint
}
Then server side we would retrieve the request
app.get('/myEndpoint', function(req, res) {
let idToken = req.get('X-FBUser-Token')
verifyToken(idToken, function(isAdmin){
if (isAdmin) {
//Fetch the key value pair and send it back to the client here
}
})
})
function verifyToken(idToken, cb) {
firebase.auth().verifyIdToken(idToken).then(function(decodedToken) {
var uid = decodedToken.sub;
firebase.database().ref('admins/' + uid).on('value', function (snap){
cb(snap.val() !== null)
})
}).catch(function(error) {
// Handle error
});
}
And then the client would receive back the response and deal with it. Everything done over HTTPS ofcourse.
Note: I know that the code above is rather crude and would need some refinement, but hopefully you get the concept
My questions:
First of all, is this a safe way of doing things?
Is there a better, more straight forward approach?

Can I get a consistent 'iss' value for a Google OpenIDConnect id_token?

I'm using Google's OpenIDConnect authentication, and I want to validate the JWT id_token returned from Google. However, the documentation seems inconsistent about what value Google returns for the iss (issuer) claim in the ID token.
One page says, "iss: always accounts.google.com", but another page says "The value of iss in the ID token is equal to accounts.google.com or https://accounts.google.com" and a comment in the example code further explains:
// If you retrieved the token on Android using the Play Services 8.3 API or newer, set
// the issuer to "https://accounts.google.com". Otherwise, set the issuer to
// "accounts.google.com". If you need to verify tokens from multiple sources, build
// a GoogleIdTokenVerifier for each issuer and try them both.
I have a server-side application, not an Android app, so I'm not using Play Services.
To further muddy the waters, the OpenIDConnect specification itself contains a note that:
Implementers may want to be aware that, as of the time of this writing, Google's deployed OpenID Connect implementation issues ID Tokens that omit the required https:// scheme prefix from the iss (issuer) Claim Value. Relying Party implementations wishing to work with Google will therefore need to have code to work around this, until such time as their implementation is updated. Any such workaround code should be written in a manner that will not break at such point Google adds the missing prefix to their issuer values.
That document is dated November 8, 2014. In the time since then, has Google standardized on an iss value, or do I really need to check for both of them? The comment above seems to indicate that only Play Services >=8.3 gets iss with https://, and everywhere else the value will be just accounts.google.com. Is that true?
You have to check both possibilities. This is what worked for me...
Decode the token to get the issuer. If the issuer is not equal to either one of https://accounts.google.com or accounts.google.com you can stop there. It's an invalid token.
If the issuer is equal to either of the above Google strings, then pass that same decoded issuer value forward to the verification step.
Following is the an implementation I wrote in JavaScript for some Node.js Express middleware:
function authorize(req, res, next) {
try {
var token = req.headers.authorization;
var decoded = jwt.decode(token, { complete: true });
var keyID = decoded.header.kid;
var algorithm = decoded.header.alg;
var pem = getPem(keyID);
var iss = decoded.payload.iss;
if (iss === 'accounts.google.com' || iss === 'https://accounts.google.com') {
var options = {
audience: CLIENT_ID,
issuer: iss,
algorithms: [algorithm]
}
jwt.verify(token, pem, options, function(err) {
if (err) {
res.writeHead(401);
res.end();
} else {
next();
}
});
} else {
res.writeHead(401);
res.end();
}
} catch (err) {
res.writeHead(401);
res.end();
}
}
Note this function uses jsonwebtoken and jwk-to-pem node modules. I ommitted details of the getPem function which ultimately converts a json web key to pem format.
To start with, I definitely agree that Google's documentation is a murky business.
There are a couple of different ways in which you can validate the integrity of the ID token on the server side (btw this is the page you're looking for):
"Manually" - constantly download Google's public keys, verify signature and then each and every field, including the iss one; the main advantage (albeit a small one in my opinion) I see here is that you can minimize the number of requests sent to Google).
"Automatically" - do a GET on Google's endpoint to verify this token - by far the simplest:
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}
Using a Google API Client Library - the overhead might not be worth it, C# doesn't have an official one etc.
I suggest you go with the 2nd option and let Google worry about the validation algorithm.

Firebase: Authenticate an existing user using REST API and Firebases hidden Auth URL

For the past 3 years we have used HTML/Js only with Firebase but now we are using Unity as well.
The current Unity/Firebase only works on Android/iOS when deployed and 99% of our work is on the windows store.
I've actually got a pretty decent Unity/Firebase codebase going but it requires me to use a full App Secret.
All the other libraries expose a method to login with Email/Password but the REST API only allows the use of a token or your app secret that it then states is ill advised to put into your client; I guess the thinking is if you're using a different library that you'll have your own auth/user method which we don't...
Now, I've pulled apart the web version and got this:
https://auth.firebase.com/v2/<myfirebase>/auth/password?&email=dennis%40<mysite>&password=<mypassword>v=js-2.2.9&transport=json&suppress_status_codes=true
So there IS an endpoint that I can send stuff to and I've tested it inside unity with good results.
Obviously the URL isn't guaranteed to stay working but I'm wondering if there is any reason NOT to use this?
Also, Why not just expose this endpoint in the official REST API?
As I understand it, that URL will continue to work for your Legacy Firebase project. You will have to do the same sort of reverse engineering if you want to update to the new Firebase 3.0 API. However, if you are still using a legacy Firebase project -- I encourage you to take a look at this. It has not been updated to work with Firebase 3.0 -- so I needed to do something similar to what you did to allow login to the new API.
I was able to do this with the new API using C# as follows (where FirebaseManager is a Singleton I wrote for Global variables and functions to write and read from/to the DB :
Hashtable loginData = new Hashtable();
loginData.Add ("email", <EMAIL-GOES-HERE>);
loginData.Add ("password", <PASSWORD-GOES-HERE>);
loginData.Add ("returnSecureToken", true);
UnityHTTP.Request loginRequest = new UnityHTTP.Request ("post",
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key="
+ <YOUR-PROJECT-API-KEY-GOES-HERE>, loginData);
loginRequest.Send ((request) => {
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error logging in. Server returned null or malformed response");
}
FirebaseManager.Instance.idToken = (string)jsonResponse["idToken"]; // This is your auth token
FirebaseManager.Instance.uid = (string)jsonResponse["localId"]; // this is your "uid"
});
// I have a list of users in my db keyed by the "uid" -- I access them like this
UnityHTTP.Request fullnameRequest = new UnityHTTP.Request ("get",
<YOUR-DATABASE-ROOT-URL-HERE>
+ "/users/" + FirebaseManager.Instance.uid + ".json?auth=" + FirebaseManager.Instance.idToken);
fullnameRequest.Send ((request) => {
Debug.Log(request.response.Text);
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error getting user info. Server returned null or malformed response");
}
FirebaseManager.Instance.fullname = (string)jsonResponse["fullname"];
FirebaseManager.Instance.groupId = (string)jsonResponse["group"]; // just storing this in memory
});
So I don't think there is any harm in using the URL, just make sure you budget time for more work when things change.

The definitive guide to posting a Facebook Feed item using pure C#

Does anyone have a definitive way to post to a user's wall, using nothing but the .NET Framework, or Silverlight?
Problems deriving from people's attempts have been asked here on SO, but I cannot find a full, clear explanation of the Graph API spec and a simple example using WebClient or some similar class from System.Net.
Do I have to send all feed item properties as parameters in the query string? Can I construct a JSON object to represent the feed item and send that (with the access token as the only parameter)?
I expect its no more than a 5 line code snippet, else, point me at the spec in the FB docs.
Thanks for your help,
Luke
This is taken from how we post to a user's wall. We place the data for the post in the request body (I think we found this to be more reliable than including all the parameters in the query part of the request), it has the same format as a URL encoded query string.
I agree that the documentation is rather poor at explaining how to interact with a lot of resources. Typically I look at the documentation for information on fields and connections, then work with the Graph API Explorer to understand how the request needs to be constructed. Once I've got that down it's pretty easy to implement in C# or whatever. The only SDK I use is Facebook's Javascript SDK. I've found the others (especially 3rd party) are more complicated, buggy, or broken than rolling my own.
private void PostStatus (string accessToken, string userId)
{
UriBuilder address = new UriBuilder ();
address.Scheme = "https";
address.Host = "graph.facebook.com";
address.Path = userId + "/feed";
address.Query = "access_token=" + accessToken;
StringBuilder data = new StringBuilder ();
data.Append ("caption=" + HttpUtility.UrlEncodeUnicode ("Set by app to describe the app."));
data.Append ("&link=" + HttpUtility.UrlEncodeUnicode ("http://example.com/some_resource_to_go_to_when_clicked"));
data.Append ("&description=" + HttpUtility.UrlEncodeUnicode ("Message set by user."));
data.Append ("&name=" + HttpUtility.UrlEncodeUnicode ("App. name"));
data.Append ("&picture=" + HttpUtility.UrlEncodeUnicode ("http://example.com/image.jpg"));
WebClient client = new WebClient ();
string response = client.UploadString (address.ToString (), data.ToString ());
}
I don't know much about .net or silverlight, but the facebook api works with simple http requests.
All the different sdks (with the exception of the javascript one) are mainly just wrappers for the http requests with the "feature" of adding the access token to all requests.
Not in all requests the parameters are sent as querystring, in some POST requests you need to send them in the request body (application/x-www-form-urlencoded), and you can not send the data as json.
If the C# sdk is not to your liking, you can simply create one for your exact needs.
As I wrote, you just need to wrap the requests, and you can of course have a method that will get a json as parameter and will break it to the different parameters to be sent along with the request.
I would point you to the facebook documentation but you haven't asked anything specific so there's nothing to point you to except for the landing page.