CVE-2022-39287 (JavaScript, published 2022-10-07T20:15Z)
Description: value: tiny-csrf is a Node.js cross site request forgery (CSRF) protection middleware. In versions prior to 1.1.0 cookies were not encrypted and thus CSRF tokens were transmitted in the clear. This issue has been addressed in commit `8eead6d` and the patch with be included in version 1.1.0. Users are advised to upgrade. There are no known workarounds for this issue.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-319 \u2014 Cleartext Transmission of Sensitive Information
The product transmits sensitive or security-critical data in cleartext in a communication channel that can be sniffed by unauthorized actors.
Repository: https://github.com/valexandersaulys/tiny-csrf
Files changed:
README.md
encryption.js
index.js
package.json
test.js
Commit message: Updated to include encrypting cookies, better protection client side


Vulnerable code:
Defaults to only requiring CSRF protection on `POST` requests and
excludes no URLs. 
This uses the built-in [`crypto`
library](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions)
for generating CSRF tokens. This may or may not be sufficient for your
needs. 
const csurf = (forbiddenMethods, excludedUrls) => {
  if (!forbiddenMethods) forbiddenMethods = ["POST"];
    if (!req.cookies || !res.cookie)
        if (!req.cookies.csrfToken) {
          const csrfToken = randomUUID();
          res.cookie("csrfToken", csrfToken);
          return csrfToken;
        }
        return req.cookies.csrfToken;
      const { csrfToken } = req.cookies;
        (req.query._csrf === csrfToken ||
          req.params._csrf === csrfToken ||
          req.body._csrf === csrfToken)
        res.cookie("csrfToken", "");
          `Did not get a CSRF token for ${req.method} ${req.originalUrl}: ${req.body._csrf} v. ${csrfToken}`
        if (!req.cookies.csrfToken) {
          const csrfToken = randomUUID();
          res.cookie("csrfToken", csrfToken);
          return csrfToken;
        }
        return req.cookies.csrfToken;
// module.exports = csurf(forbiddenMethods, excludedUrls);
  "version": "1.0.3",
    this.csrf = csurf(null, []);
      cookies: {},
      cookies: {
        csrfToken: "aaaa"
      cookies: {
        csrfToken: "aaaabbb"
      assert.include(err.message, "Did not get a CSRF token");
      cookies: {
        csrfToken: "aaaabbb"
      assert.include(err.message, "Did not get a CSRF token");
      cookies: {},
      assert.include(err.message, "Did not get a CSRF token");
  it("will reuse if the CSRF token already exists in a non-POST", () => {
    const req = mockRequest({
      method: "GET",
      cookies: {
        csrfToken: "aaaabc"
      },
      body: {}
    });
    const res = mockResponse({
      cookie: sinon.stub()
    });
    const next = sinon.stub();
    assert.isNotFunction(req.csrfToken);
    this.csrf(req, res, next);
    assert.isTrue(next.calledOnce);
    assert.isFunction(
      req.csrfToken,
      "Did not attach a csrfToken function to req"
    );
    const sampleToken = req.csrfToken();
    assert.equal(sampleToken, "aaaabc");
  });
    this.csrf = csurf(["POST", "PUT"], []);
      cookies: {
        csrfToken: "aaaa"
      cookies: {
        csrfToken: "aaaabbb"
      assert.include(err.message, "Did not get a CSRF token");
      cookies: {
        csrfToken: "aaaa"
    this.csrf = csurf(null, ["/detail", /\/detail\.*/i]);
      cookies: {
        csrfToken: "aaaaa"
    const sampleToken = req.csrfToken();
    assert.equal(sampleToken, "aaaaa", "Not reusing csrf tokens");
      cookies: {
        csrfToken: "aaaaa"
    const sampleToken = req.csrfToken();
    assert.equal(sampleToken, "aaaaa", "Not reusing csrf tokens");
      cookies: {},
      cookies: {
      assert.include(err.message, "Did not get a CSRF token");

Fixed code:
Note that if you require very specific security needs you may want to
look elsewhere. This library supports encrypting cookies on the client
side to prevent malicious attackers from looking in. It does not
protect against things such as [double submit
cookies](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie). Those
setups require greater setup and more know-how. This library aims to
be simple to setup. 

const session = require("express-session");
app.use(
  session({})
);
    "123456789iamasecret987654321look",  // secret -- must be 32 bits or chars in length
The secret must be 32 bits (e.g. characters) in length and uses 
[the built-in `crypto.createCipheriv` library built into Node
](https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options). The
secret length is enforced by the
[`AES-256-CBC`](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
algorithm. 
Defaults to only requiring CSRF protection on `POST`, `PUT`, and `PATCH` requests and
excludes no URLs. The csrf will be checked for in the body of a
request via `_csrf`. 


## License

[MIT](#)
const { randomBytes, createCipheriv, createDecipheriv } = require("crypto");

const ALGORITHM = "aes-256-cbc";

const encryptCookie = (cookie, _secret) => {
  /**
   * Encrypt a cookie using AES 256 bits
   * @param {cookie} string the cookie we want to encrypt. Will be visible as plain string to client.
   * @param {_secret} string the secret that will be stored server-side. Client will never see this.
   */
  const iv = randomBytes(16);
  const _cipher = createCipheriv(ALGORITHM, Buffer.from(_secret), iv);
  const encrypted = [
    iv.toString("hex"),
    ":",
    _cipher.update(cookie, "utf8", "hex"),
    _cipher.final("hex")
  ];
  return encrypted.join("");
};

const decryptCookie = (cookie, _secret) => {
  /**
   * Decrypt a cookie using AES 256 bits
   * @param {cookie} string the cookie we want to encrypt. Will be visible as plain string to client.
   * @param {_secret} string the secret that will be stored server-side. Client will never see this.
   */
  const _encryptedArray = cookie.split(":");
  if (_encryptedArray.length != 2) throw new Error("bad decrypt");
  const iv = new Buffer(_encryptedArray[0], "hex");
  const encrypted = new Buffer(_encryptedArray[1], "hex");
  const decipher = createDecipheriv(ALGORITHM, _secret, iv);
  const decrypted =
    decipher.update(encrypted, "hex", "utf8") + decipher.final("utf8");
  return decrypted;
};

const verifyCsrf = (requestCsrf, cookieCsrf, _secret) => {
  /**
   * Verify a CSRF token
   * @param {requestCsrf} string the CSRF coming from client side
   * @param {cookieCsrf} string the CSRF as stored in the user's cookies
   * @param {_secret} string the string used to encrypt the CSRF in the first place.
   */
  try {
    const decryptedCookie = decryptCookie(cookieCsrf, _secret);
    return decryptedCookie === requestCsrf;
  } catch (err) {
    return false;
  }
};

module.exports = {
  encryptCookie,
  decryptCookie,
  verifyCsrf
};
const { encryptCookie, verifyCsrf } = require("./encryption");
const cookieParams = {
  httpOnly: true,
  sameSite: "strict",
  signed: true,
  maxAge: 300000
};

const csurf = (secret, forbiddenMethods, excludedUrls) => {
  if (!forbiddenMethods) forbiddenMethods = ["POST", "PUT", "PATCH"];
  if (secret.length != 32)
    throw new Error("Your secret is not the required 32 characters long");
    if (!req.cookies || !res.cookie || !req.signedCookies)
        const csrfToken = randomUUID();
        res.cookie("csrfToken", encryptCookie(csrfToken, secret), cookieParams);
        return csrfToken;
      const { csrfToken } = req.signedCookies;
        verifyCsrf(req.body?._csrf, csrfToken, secret)
        res.cookie("csrfToken", null, cookieParams);
          `Did not get a valid CSRF token for '${req.method} ${req.originalUrl}': ${req.body?._csrf} v. ${csrfToken}`
        const csrfToken = randomUUID();
        res.cookie("csrfToken", encryptCookie(csrfToken, secret), cookieParams);
        return csrfToken;
  "version": "1.1.0",
const { randomUUID, randomBytes } = require("crypto");
const { encryptCookie, decryptCookie, verifyCsrf } = require("./encryption");

describe("Cookie Encryption Tests", () => {
  before(() => {});
  after(() => {});

  it("will encrypt and decrypt a cookie", () => {
    // req.cookies.csrfToken =>  will be scrambled
    const secret = "123456789iamasecret987654321look";
    const csrfToken = randomUUID();
    const encryptedCsrfToken = encryptCookie(csrfToken, secret);
    assert.notEqual(
      csrfToken,
      encryptedCsrfToken,
      "Both are equal as plain string and shouldn't be"
    );
    assert.isTrue(verifyCsrf(csrfToken, encryptedCsrfToken, secret));
    assert.isFalse(
      verifyCsrf(
        csrfToken,
        encryptedCsrfToken,
        randomBytes(16).toString("hex")
      ),
      "Should not be able to verify without the secret"
    );
    assert.isFalse(
      verifyCsrf(randomUUID(), encryptedCsrfToken, secret),
      "Should not verify random UUID tokens"
    );
    assert.isFalse(
      verifyCsrf("", encryptedCsrfToken, secret),
      "Should not verify random blank tokens"
    );
    assert.isFalse(
      verifyCsrf(null, encryptedCsrfToken, secret),
      "Should not verify random null tokens"
    );
  });
});
    this.secret = "123456789iamasecret987654321look";
    this.csrf = csurf(this.secret);
  it("throw internal error if our secret is not long enough", () => {
    assert.throws(() => csurf("imnotlongenough"));
  });
      signedCookies: {},
      signedCookies: {
        csrfToken: encryptCookie("aaaa", this.secret)
      signedCookies: {
        csrfToken: encryptCookie("aaaabbb", this.secret)
      assert.include(err.message, "Did not get a valid CSRF token");
      signedCookies: {
        csrfToken: encryptCookie("aaaabbb", this.secret)
      assert.include(err.message, "Did not get a valid CSRF token");
      signedCookies: {},
      assert.include(err.message, "Did not get a valid CSRF token");
    this.secret = "123456789iamasecret987654321look";
    this.csrf = csurf(this.secret, ["POST", "PUT"], []);
      signedCookies: {
        csrfToken: encryptCookie("aaaa", this.secret)
      signedCookies: {
        csrfToken: encryptCookie("aaaabbb", this.secret)
      assert.include(err.message, "Did not get a valid CSRF token");
      signedCookies: {
        csrfToken: encryptCookie("aaaa", this.secret)
    this.secret = "123456789iamasecret987654321look";
    this.csrf = csurf(this.secret, null, ["/detail", /\/detail\.*/i]);
      signedCookies: {
        csrfToken: encryptCookie("aaaaa", this.secret)
      signedCookies: {
        csrfToken: encryptCookie("aaaaa", this.secret)
      signedCookies: {},
      signedCookies: {
      assert.include(err.message, "Did not get a valid CSRF token");