Registered Information Security Specialist Exam, Spring 2024 (Reiwa 6) PM Q1 Commentary — JWT alg=none, API Authorization, and Interim WAF Mitigation

· · Registered Information Security Specialist, Registered Security Specialist, API, API Security, JWT, Authentication, Authorization, WAF, Log4Shell, Information Security, Vulnerability, IPA

“We verify the JWT’s signature, so the user ID can be trusted.”

That statement is only half right.

Question 1 of the PM (afternoon) session of the Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination is built around an API called from a smartphone app.1 On successful authentication a JWT is issued, and that JWT is attached to calls against the API that fetches and updates user information. At first glance, this is an entirely ordinary setup.

However, the assessment turns up the following four issues.

  1. Changing the JWT header’s alg to none lets an unsigned JWT pass.
  2. Keeping a valid JWT but changing mid to a different user ID lets you read or update someone else’s information.
  3. Adding an undocumented status=paid turns a free-tier user into a paid user.
  4. The four-digit authentication code delivered by email can be brute-forced with no limit on attempts.

All four look like “authentication-adjacent vulnerabilities”, but the causes are not the same. What is being broken are separate boundaries: token integrity, object-level authorization, property-level authorization, and attempt-rate limiting.

The second half of the question adds yet another topic. A critical vulnerability is disclosed in a widely used open-source library, one that lets an attacker execute code remotely by abusing JNDI Lookup. Neither a fix nor a finished WAF rule exists yet. In the meantime, the question asks how to confirm the impact, where the WAF should look, and why the initial WAF mode should be “detect” rather than “block”.

This article uses the official model answers2 and the grading commentary3 as its foundation, and works through not just the answer to each question but why that is the answer, and how much more rigorously you should design for it in practice.

Overview of the questionShows the trust boundary broken at each stage - authentication code, JWT, API authorization, and library vulnerabilityNo attempt limitAllows alg=noneTrusts midstatus=paidJNDI/LDAP/HTTPUser app4-digit auth codeJWT issuanceUser APILog outputVulnerable libraryRemote code executionRead/update another user's dataChange billing status

Figure 1: Overview of the question. A different trust boundary is broken at each stage.

1. The Bottom Line First

  • The property of a RESTful API not holding session state is called statelessness. This does not mean the server holds no database or user state whatsoever
  • A four-digit authentication code has 10,000 possible values. At 10 attempts per second, an attacker succeeds after an average of 5,000 attempts, i.e. 500 seconds — shorter than the 10-minute validity period, so expiry alone cannot stop it
  • The minimum countermeasure against alg=none is confirming that the JWT header’s alg is not NONE. In practice, though, you should fix the set of allowed algorithms on the server side
  • Even with a valid JWT, the request’s mid must not be trusted. Either match the user ID inside the JWT against mid, or, more safely, don’t accept mid from the client at all and determine the target from the JWT
  • Adding status=paid is a Mass Assignment problem, where properties outside the specification get bound straight onto the internal object. Use the update DTO as an allowlist, and never let the user change billing status
  • The model answer for the brute-force countermeasure is logic that locks the account once the number of consecutive failures exceeds a threshold. In practice, layer on graduated delays and per-source controls as well
  • To confirm the impact of a newly disclosed critical vulnerability, rather than issuing a destructive command, record accesses to a test server’s index.html to confirm that remote code execution actually reaches it
  • Because the attack string is carried in an HTTP header, the WAF’s inspection target is Header. As a regular expression that handles case swapping, use something like \W[jJ][nN][dD][iI]\W
  • The benefit of starting the WAF in “detect” mode is that it prevents legitimate business traffic from being blocked by a false positive. When an alert arrives, examine whether it is a genuine attack, then move to blocking mode once the rule has been tuned
  • The WAF is only an interim measure; the root fix is updating the affected library to a patched version

2. How the Scenario Maps to the Questions

The scenario is set at Company G, which is launching a new healthcare service. Users enter data such as meals and body weight through a smartphone app and receive health-risk assessments and dietary advice. The system is built on the cloud, combining an API gateway, event-driven processing, and a managed database.

The question paper abstracts away specific product and service names. This article likewise does not reproduce IPA’s diagrams or text, but paraphrases only the structure needed to understand the questions.

Question Topic Chapter of this article
Question 1 The nature of RESTful APIs Chapter 4
Question 2(1) Time to brute-force the 4-digit code Chapter 5
Question 2(2) JWT alg=none Chapter 6
Question 2(3) Accessing another user via mid Chapter 7
Question 2(4) The flaw that accepts an out-of-spec status Chapter 8
Question 2(5) Brute-force countermeasures Chapter 9
Question 3(1) Safely confirming that a vulnerability exists Chapter 11
Question 3(2)(3) Where the WAF looks, and the regular expression Chapter 12
Question 3(4) The benefit of detect mode and how to operate it Chapter 13

The grading commentary notes that the overall correct-answer rate was about average. It also points out, however, that the correct-answer rate was somewhat lower for the JWT-tampering countermeasure in Question 2(2) and for the mechanism needed on the verification server in Question 3(1). Neither can be answered from vocabulary alone. You have to trace which value the attacker changed, which process it flowed into, and where it ended up being trusted.

3. This Is Not Just One “Authentication Problem”

Laying the whole question out by trust boundary gives the following.

[User ID / password]
          |
          v
[4-digit code check] ---- no attempt limit ----> brute force
          |
          v
[Issue JWT]
          |
          v
[JWT library] ------- allows alg=none ------> user ID tampering
          |
          v
[User API]
    |             |
    |             +-- passes status through wholesale ----> property-level authorization flaw
    |
    +-- trusts mid -----------------------> object-level authorization flaw

[Log external input]
          |
          v
[Vulnerable library] ---- JNDI/LDAP/HTTP ------> remote code execution

The most important distinction here is the following.

Check Question it asks Broken example in this scenario
Authentication Who are you Brute-forcing the 4-digit code
Token verification Has that identity information been tampered with alg=none
Object-level authorization May this user access this user’s data Swapping mid
Property-level authorization May this field be changed status=paid
Input-to-execution boundary Is external input being interpreted as a command JNDI Lookup

Passing one check is never a reason to skip the next one. A user with a valid JWT is not necessarily allowed to read someone else’s data. A user allowed to update their own data is not necessarily allowed to change their billing status too.

Once you can separate these stages, the answer to each question stops being something you memorise.

The difference between authentication and authorizationAuthentication confirms the subject, authorization confirms what that subject is permitted to doAuthenticationwho you areAuthorizationwhat you may do

Figure 2: The difference between authentication and authorization. Authentication comes first; authorization is a separate check.

4. Question 1 — What Does “Stateless” Mean

Question 1 asks about one of the design principles of RESTful APIs: the property of not performing session management.

The answer is stateless.

Stateless means the server does not need to remember the conversational state of the previous request, because each request on its own carries everything needed to process it. In this question, the smartphone app attaches a JWT to the Authorization header on every request. The server verifies that JWT and identifies the user for that request from it.

A common misreading is to take “stateless” as “the server holds no state at all”. In reality, it ordinarily holds the following state.

  • The database storing user information and health data
  • Billing status
  • The authentication code’s value, expiry, and failure count
  • The JWT signing key
  • Revocation information, for designs that use a revocation list
  • Logs and audit records

What it does not hold is server-side session state that exists purely to continue a conversation, as a precondition each API call depends on.

Being stateless also does not automatically improve security. Sending the JWT on every request does make horizontal scaling easier, but if the JWT verification is wrong, that error spreads uniformly across every node too. An architectural property and security correctness are two different things.

5. Question 2(1) — A Four-Digit Code Is Cracked in an Average of 500 Seconds

The authentication API sends a four-digit number by email once the user ID and password match. It then issues a JWT once the user ID and the four-digit code match. The code is valid for 10 minutes from generation.

In the assessment, 10 attempts per second were possible. The question asks how many seconds it takes, on average, to break through.

The Calculation Is “Half the Candidate Space”

A four-digit number, including a leading zero, has the following 10,000 possibilities.

0000, 0001, 0002, ... , 9999

If the correct answer is drawn uniformly at random, an attacker trying candidates in order without repeats reaches the correct one, on average, after half the candidate space.

Average number of attempts = 10,000 / 2 = 5,000
Average time                = 5,000 / 10 attempts per second = 500 seconds

So blank b is 500.

The worst case takes up to 1,000 seconds, but the question asks for the average. And the code’s validity period is 600 seconds — longer than the average break time of 500 seconds. That is why it is judged “likely to be broken”.

Sense of scale for the 4-digit auth codeTrying 10,000 candidates at 10 per second averages 5,000 attempts and 500 seconds, which is less than the 600-second validity periodAverage 10,000 / 2 = 5,000 attempts500 seconds is less than 600 seconds10,000 candidatesAverage break time 500 secondsValidity period 600 secondsCan be broken within the validity period

Figure 9: Sense of scale for the four-digit authentication code. Trying, on average, half the candidate space breaks it within the validity period.

Shortening Only the Expiry Time Loses If the Candidate Space Is Small

The strength of an authentication code is determined by neither the digit count alone nor the validity period alone.

Number of attempts possible during the validity period
= attempts per second x validity period
= 10 x 600
= 6,000 attempts

Trying non-repeating values in order, an attacker can check 60% of the 10,000 possibilities within the validity period. Setting an expiry time is not sufficient on its own if the number of attempts is not limited.

The current NIST SP 800-63B requires at least six digits for short-term secrets used in out-of-band authentication, and mandates an attempt-rate limit whenever the secret has fewer than 64 bits of entropy. It also calls for email not to be used for out-of-band authentication.4 The exam answer works within the given specification of a four-digit code sent by email, but for a new design in practice, that premise itself should be reconsidered.

6. Question 2(2) — alg=none Is a Problem of “Letting the Attacker Choose the Verification Method”

The JWT in this question consists of three parts: header, payload, and signature.

base64url(header).base64url(payload).base64url(signature)

The header recorded RS256 as the algorithm used for signing. The payload contains the user ID, issue time, and expiry.

The assessor changed the following two things.

  1. Change the header’s alg from RS256 to NONE.
  2. Change the payload’s user ID to a different user.

Sending that JWT, verification succeeded and let the assessor impersonate someone else.

Flow of the JWT alg=none attackChanging alg to none in a valid JWT and rewriting the user ID lets the request throughChange header alg to noneSkips signature verificationValid JWTalg=RS256user=user01Tampered JWTalg=noneuser=user02Server accepts itas user02

Figure 3: Flow of the JWT alg=none attack. The attacker chooses the verification algorithm.

none Is Not a Typo

RFC 7519 defines an “Unsecured JWT” — a JWT with neither a signature nor encryption, whose alg is none.5 So the value none is not something that simply doesn’t exist in the spec.

The problem is that an API that should accept only signed JWTs accepted the none the attacker specified.

Written conceptually, the vulnerable process looks like this.

1. Read the JWT header.
2. Look at the alg written in the header, and choose the verification method.
3. If alg is none, do not verify the signature.
4. Trust the user ID in the payload.

The very strength of the security is being chosen from input the attacker controls.

The Exam Answer

The question asks, in 20 characters or fewer each, what data the fixed library Q should verify, and what that verification should check.

The model answer is as follows.

Item Gist of the answer
Data to verify The value specified in the JWT header’s alg
What to verify That it is not NONE

As a direct fix for the vulnerability described in the question, this is correct.

In Practice, Don’t Settle for “Anything but NONE”

Here we need to separate the exam answer from the practical recommendation.

RFC 8725 states that a JWT library should let the caller specify a set of permitted algorithms, and that nothing outside that set may be used.6 In other words, the idea is this.

Bad approach:
  Accept if token.header.alg != "none"

Good approach:
  Accept only if it is contained in serverConfig.allowedAlgorithms
  e.g. allowedAlgorithms = ["RS256"]

Rejecting only none can still leave other weak algorithms, or the possibility of algorithm confusion where a public-key scheme is mistaken for a symmetric-key one. The principle is not to keep adding negative conditions for what to accept, but to fix a narrow, positive set of what is allowed.

JWT verification should confirm not just the algorithm but, depending on the use case, at least the following as well.

Item What to confirm
Signature Can it be verified with the expected key and algorithm
iss Is it a trusted issuer
aud Was the token issued for this API
exp Is it within its validity period
nbf Is it not before its “not valid before” time
sub or user ID Is it a valid subject within the application
Token type Is an ID token being confused with an access token, etc.

In this question the payload’s key name is user, but in practice you should either use the standard sub or clearly define the meaning of a custom claim.

Safe versus unsafe JWT verificationUnsafe verification depends on alg, safe verification uses a server-side allowlistSafe verificationServer-configured allowed algorithmse.g. RS256Confirm the JWT header's algis in the allowlistVerify signature, iss, aud, expUnsafe verificationRead alg from the JWT headerAccept if alg is none

Figure 4: Safe versus unsafe verification. In practice, fix a narrow set of allowed algorithms.

Base64url Is Not Encryption

There is another common misunderstanding about JWTs. The header and payload are represented in base64url, but that is not encryption. Anyone can decode and read them.

What the signature guarantees, and only when it verifies correctly, is that the content has not been tampered with since issuance. It does not mean personal information you want kept secret may be put in the payload of a signed JWT.

7. Question 2(3) — Even With a Valid JWT, Changing mid Read Someone Else’s Data

Next is an attack that does not tamper with the JWT itself.

The user API receives a user ID called mid on GET or PUT. Common module P fetches or updates, in the database, the user information tied to that mid.

The attack’s structure is simple.

User ID inside the JWT: user01    <- a correctly signed JWT
Request's mid:          user02   <- changed by the attacker

The JWT’s signature is valid, so authentication succeeds. But the API trusts mid=user02 as given, and returns user02’s information.

This is a textbook case of what the OWASP API Security Top 10 2023 calls Broken Object Level Authorization (BOLA). Whenever data is accessed using an object ID that the user has specified, authorization for that specific object must be checked every time.7

The BOLA attackUsing a valid JWT while changing the request's mid to a different user IDJWT user01mid user02Trusts midAttackerUser APIReturns user02's data from the DB

Figure 5: The BOLA attack. Authentication passes, but authorization was never checked.

The Question’s Answer

Underline 2 in Table 5 asks, in 40 characters or fewer, for the processing to add to the call into common module P.

The model answer is:

Logic that verifies whether the user ID contained in the JWT matches the value of mid

The benefit of verifying inside common module P is that the same authorization check is easy to apply to both GET and PUT, and to any future API that uses P as well. Copying the same comparison into each screen or endpoint individually means it will be missing somewhere.

A Safer Design Is Not to Accept mid at All

For an API that only fetches or updates the caller’s own information, there is no need to accept a user ID from the client at all.

GET /users/me
Authorization: Bearer <JWT>

On the server side, the subject is extracted from the verified JWT.

principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)

The same applies to updates.

principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
    userId = principal.subject,
    name = input.name,
    age = input.age
)

A comparison check protects you if you write it. But a design that never accepts the target ID from outside reduces the very existence of the bug class where you forget to write that comparison.

If an administrator needs to operate on another user’s information, split it as follows.

PUT /users/me                  For general users
PUT /admin/users/{userId}      For administrators

For the administrator route, require separate permissions, audit logging, and, if needed, re-authentication. This makes the authorization policy’s boundaries much more visible than “add an exception to the general-user API for administrators only”.

How to prevent BOLAInstead of using the request's mid, decide or check the target from the JWT's subjectGET /users/me + JWTTake sub from JWTMatchNo matchNo midUserAPIIf mid is presentdoes it match subReturn own data403 rejectDB lookup by JWT sub

Figure 6: How to prevent BOLA. Either don’t accept mid, or check it against the JWT’s subject.

Distinguishing Authentication From Authorization in One Sentence

Both on the exam and in practice, the following phrasing helps.

  • Authentication: who you are
  • Authorization: what that person may do

A successful JWT signature verification only gets you as far as “the subject this token represents can be trusted”. Whether “that subject may read user02” must be confirmed separately.

8. Question 2(4) — status=paid Is a Property-Level Authorization Flaw

The user API’s specification defines the following as update parameters.

mid   User ID
name  Name
age   Age

However, the assessor added the following value, which is not in the specification.

status=paid

The status of a free-tier user then changed to that of a paying user.

According to the question, service L did not validate the parameters it received; it passed all of them straight to common module P, which was built so it could update the database directly.

The answer for blank c is common module P.

Mass AssignmentAn out-of-spec status=paid is added and applied wholesale onto the internal objectAttacker adds status=paidAuto-boundSaved to DBAPI specmid / name / ageRequest bodyCommon module PBilling status changed to paid

Figure 7: Mass Assignment. An out-of-spec property is applied wholesale onto the internal object.

The Difference From BOLA

The mid swap from the previous chapter and this status addition look similar, but the granularity being protected differs.

Vulnerability What the attacker changes What should actually be checked
mid swap The target object May this user access this user record
status addition A property within the object May this user change this field

The OWASP API Security Top 10 2023 treats the latter as Broken Object Property Level Authorization, folding what was previously called Mass Assignment into this category.8

“Putting JSON Straight Into the Entity” Is Dangerous

The vulnerable implementation, conceptually, looks like this.

entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)

Even if the screen only has input fields for name and age, an attacker can craft the HTTP request directly. A field’s absence from the UI is not a security boundary.

A safe implementation makes the updatable fields explicit.

input = parseExactSchema(body, fields = ["name", "age"])

entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age  = input.age
repository.save(entity)

Two things matter here.

  1. The update input type should hold only the fields a user is allowed to change.
  2. Rather than silently ignoring unknown, out-of-spec fields, reject them as an error if possible.

Silently ignoring unknown fields hides the fact that an attack failed, but it also lets you miss client implementation mistakes and signs of an attack. Unless there is a compatibility reason not to, rejecting with a strict schema makes investigation easier.

Property-level authorizationThe update DTO holds only an allowlist, and unknown properties are rejectedSchema validationYesNoDedicated pathUpdate DTO - allowlistnameageRequest bodyOnly allowedfields presentUpdate entity name/ageReturn an errorPayment serviceverified notificationUpdate status=paid

Figure 8: Property-level authorization. Restrict updatable fields with an allowlist, and change billing status only through a separate path.

status Should Change Only From the Payment Result

status=paid is not part of the user profile. It is state derived from a server-side fact: that payment succeeded.

User profile update
  -> only name / age can be changed

Verified notification from the payment service
  -> match paymentId
  -> prevent duplicate processing
  -> change status to paid

Even when stored in the same database column, the authority to change it and the path used to change it are different things. Using an internal entity directly as an external API’s input type erases that boundary.

9. Question 2(5) — Brute-Force Countermeasures Hold the Failure Count as State

For the brute-forcing of the four-digit code, blank d in Table 5 asks, in 30 characters or fewer, for the processing that belongs there. The threshold is 10.

The model answer is:

Logic that locks the account once the number of consecutive failures exceeds the threshold

This does not contradict the statelessness from Question 1. Not holding an API call’s conversational state as a server session, and persisting the failure count needed for a security decision, are two different things.

With and without an attempt-rate limitWithout a limit, the code is broken in an average of 500 seconds, but a failure-count limit sharply slows the attackWith a limitAttack speed collapsesAccount lockedLock after 10 failuresGraduated delayWithout a limitAbout 500 secondsAuthentication succeeds10 attempts per second

Figure 10: With and without an attempt-rate limit. A failure-count limit can practically stop brute-forcing.

In Practice, Don’t Rely Solely on Permanent Locking

A per-account attempt limit is necessary, but if an attacker knows someone else’s user ID, they can deliberately fail 10 times to lock the legitimate user out. In practice, therefore, combine the following.

Control Role
Per-account failure count Stops brute-forcing against a single account
Graduated wait times Tolerates a legitimate user’s input mistakes while slowing the attack down
Controls by source IP, device, ASN, etc. Suppresses attacks that try a small number of times against many accounts
Risk-based decisions Applies stronger restrictions for unusual regions, devices, or speeds
Notifying the user Lets the user notice an attack or their own mistake
A safe recovery procedure Keeps the unlock channel from becoming an attack route itself

Furthermore, when a code is resent, the failure count must not be reset to zero — otherwise an attacker can replenish their attempt budget every time they call the resend API. The current NIST SP 800-63B likewise requires that the failure count not be reset even when a new authentication secret is generated.4

Make the Authentication Code Single-Use

The question focuses on the expiry time, but in practice the following are also needed.

  • Invalidate a successful code immediately.
  • Reject reuse of the same code.
  • Do not leave the code itself in logs.
  • Make the response such that success or failure of the code check cannot be used to infer whether a user exists.
  • Put an attempt limit on the code-sending API as well.

As long as a short secret is being used, security cannot be left to random generation alone.

Authentication code countermeasuresBeyond digit count and expiry, protect with attempt limits, reuse rejection, notification, and moreAuthentication codeIncrease digit countShorten the expiryAttempt-rate limitInvalidate after successDon't reset failure count on resendDon't leave the code in logsPer-source control

Figure 11: Authentication code countermeasures. Combine digit count and expiry with attempt control and operational practices.

10. Distinguishing the Four Parts of Question 2 on One Page

The points in Question 2 that are easy to conflate, organised by the value the attacker controlled.

Attack Value the attacker changed What should not have been trusted Root fix
JWT tampering JWT header’s alg, payload’s user ID The verification algorithm declared by the token itself Fix the allowed algorithms on the server side
Reading another user’s information Request’s mid The target ID specified by the client Match against the JWT’s subject, or determine the target ID from the JWT
Upgrading to a paid user Out-of-spec status All auto-bound properties Make updatable properties an allowlist
Breaking the 4-digit code Candidates for otp Unlimited authentication attempts Add attempt-rate limits, delays, and risk decisions

It matters not to lump all of this together as “validate the input”.

  • alg is a cryptographic policy.
  • mid is object-level authorization.
  • status is property-level authorization.
  • otp is resistance to online guessing.

Even within the same HTTP request, the reason each one must be protected is different.

11. Question 3(1) — Confirming Remote Code Execution Without Causing Damage

After the service launches, a critical vulnerability V is disclosed in library H, a widely used open-source library. The question’s sequence of events is as follows.

  1. The attacker puts a string containing a JNDI Lookup into an HTTP header and sends it.
  2. The target server logs that value.
  3. The vulnerable library evaluates the JNDI Lookup and queries the attacker’s LDAP server.
  4. The LDAP response returns the URL of the attacker’s HTTP server.
  5. The target server fetches the class file and executes the command.

With the specific product name withheld, this reads as an attack of the Log4Shell (CVE-2021-44228) type. Apache’s own description likewise describes the vulnerability as one where, if an attacker controls log messages or parameters, they can execute arbitrary code loaded from an LDAP server.9

Confirmation flow for a Log4Shell-type vulnerabilityUse a harmless callback to confirm whether the chain from JNDI to remote code execution actually goes throughInject a jndi/ldap payloadinto x-api-versionJNDI LookupHTTP URL responseRecord the GETConfirm reachabilityAttackerVulnerable serverLog processingMalicious LDAP serverMalicious HTTP serverindex.htmlTest serveraccess logVulnerability confirmed

Figure 12: Confirmation flow for a Log4Shell-type vulnerability. Reachability is confirmed by recording an HTTP access rather than issuing a destructive command.

The Verification Code Triggers Only a Harmless HTTP Access

Company G runs verification code that has no impact on the system, to confirm whether vulnerability V can be exploited from outside. The only command the verification code issues is fetching the test server’s index.html.

Question 3(1) asks what needs to be implemented on the test server in order to confirm that the command was executed.

The model answer is:

A mechanism that records and lets you confirm accesses to the test server’s index.html

If the web server’s access log records a GET from the target server, that confirms, at minimum, that the following chain went through.

External HTTP request
  -> log processing
  -> JNDI Lookup
  -> LDAP response
  -> class retrieval
  -> verification command execution
  -> HTTP access to the test server

Why “Displaying Text on Screen” Is Not Enough

The target of the attack is the server. There is no guarantee that anything changes on the user’s browser screen. Also, even where the vulnerability exists, the outbound communication partway through can be blocked by a firewall.

Recording the access on the test server side produces observable evidence that the target server actually reached the outside world.

When performing this kind of verification in practice, always observe the following.

  • Obtain explicit authorisation from the owner of the target system.
  • Use a verification method that has no impact on production, or an acceptably small one.
  • Do not use destructive commands such as writes, deletes, or configuration changes.
  • Manage the verification domain and server yourself.
  • Record the verification time, source, target, and expected callback.
  • Tear down any temporary LDAP or HTTP servers and credentials after verification.

“Confirming that arbitrary code execution is possible” and “executing arbitrary dangerous code” are not the same thing. Keep the side effects to the minimum needed to meet the goal.

12. Question 3(2)(3) — The WAF Inspects the HTTP Header

Service N’s WAF lets you choose GET, POST, PUT, ANY, Header, COOKIE, or Multipart as the inspection target.

The attack code goes into the value of an HTTP header called x-api-version. So blanks e and f in Table 6 are both Header.

Map the Location Given in the Text Directly to the WAF’s Inspection Target

This is less about general knowledge and more about reading the data flow in the question text.

Where the attack string is placed:
  x-api-version header
          |
          v
The WAF's inspection target:
  Header

It is neither a GET parameter nor a POST body. Rather than looking at the WAF’s list of features and picking ANY because “it looks like an attack”, answer with the location where the question text says the attacker put the value.

Handling Case Swapping

The first proposal was, conceptually, the following rule.

Header  \Wjndi\W  block
Header  \Wldap\W  block

But swapping case, as in jNdI, evades a pattern that only matches lowercase.

The model answer for Question 3(3) is either of the following.

\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W

In the question booklet the backslash can look like a yen sign in the Japanese-locale glyph, but as a regular expression it is \W. \W matches any character other than alphanumerics and underscore. In JNDI Lookup syntax, non-word characters such as ${ and : appear immediately before and after jndi, and the pattern is written to catch those too.

The same idea can be applied to make the ldap side case-insensitive too.

\W[lL][dD][aA][pP]\W

Don’t Treat This Regex as a “Complete Log4Shell Countermeasure”

The exam is asking for a regular expression that handles the evasion technique shown in the question text. Real-world attacks can involve string splitting, alternative Lookups, encoding, other protocols, and other variations that are hard to cover with signatures alone.

The practical positioning, therefore, is as follows.

  1. Interimly block currently known attack patterns with the WAF.
  2. Investigate whether the affected library is actually present.
  3. Restrict outbound LDAP, RMI, and unnecessary HTTP traffic.
  4. Update to a patched version.
  5. After updating, still check the logs and investigate whether a breach occurred.

The WAF is a layer that buys time until a patched version is available.

Where the WAF fitsThe WAF is an interim mitigation layer, the root fix is updating the library to a patched versionWAF ruledetect/blockRestrict outbound trafficCritical vulnerability disclosedConfirm impactInterim mitigationTemporarily stop the attack patternClose off the abuse routeUpdate to the patched libraryAfter-the-fact review and prevention

Figure 13: Where the WAF fits. The WAF only buys time until a patch arrives; the root fix is the update.

13. Question 3(4) — Why Start With “Detect”

For the updated WAF rule, Z, a Registered Security Specialist, advises setting the mode to “detect” rather than “block” for a fixed period after it goes live in production.

The question asks, in 25 characters or fewer each, for the benefit of using detect mode and for what should be done to minimise damage.

The model answer is:

Item Gist of the answer
Benefit It can prevent blocking caused by a false positive
What to do Examine whether it is an attack whenever an alert is received

Detect Mode Is Not a “Do Nothing” Mode

In detect mode, traffic that matches the rule is still let through, but it is logged and an alert is raised. Even if the string jndi or ldap happens to appear inside a legitimate API call, business is not stopped immediately.

In exchange, the operations side needs to do the following.

Alert received
   |
   v
Check the request in question
   |
   +-- Legitimate traffic -> narrow the rule, consider an exception
   |
   +-- Attack             -> isolate the target, preserve logs, investigate impact, move to blocking

If no one looks at the alerts, detect mode has no protective effect at all. Detection only works paired with an operational process that observes and judges.

The Path From Detect to Block

A typical rollout procedure is as follows.

  1. Run detect mode against real traffic.
  2. Classify hits as false positives or true positives.
  3. Tune the target header, path, API, word boundaries, and so on.
  4. Confirm that the impact on legitimate traffic is acceptable.
  5. Switch to blocking mode.
  6. Monitor the number of blocks and the business impact.

This, though, is the principle for ordinary times. When a vulnerability is critical, is actively being exploited, and no alternative exists, the outage caused by a breach can be judged to outweigh that caused by a false positive, and blocking from the start is chosen instead. In the exam’s scenario, detect mode is chosen first in order to confirm that the service can keep operating as before.

From WAF detect mode to block modeObserve alerts in detect mode, tune out false positives, then move to block modeReal trafficFalse positiveAttackDetect modeAlert raisedAttack orfalse positiveTune the ruleSwitch to block modeMonitor block count and business impact

Figure 14: From detect to block. Observe and tune first, confirm the impact is acceptable, then move to blocking.

14. The WAF Is an Interim Measure; Updating Is the Root Fix

In the question, library H’s official site had neither a fix nor an interim workaround yet, and even the cloud provider’s comprehensive WAF rule was going to take up to 72 hours. So Company G confirms the impact itself and provisionally blocks at least the patterns it has already identified.

This sequence is the basic shape of incident response.

Stage Purpose Response in this question
Confirm impact Judge whether your own organisation is genuinely at risk Confirm exploitability from outside with a harmless callback
Interim mitigation Buy time until a fix arrives WAF rules, detect/block, outbound traffic restrictions
Root fix Remove the vulnerable cause Update to a patched library
After-the-fact review Check whether it has already been exploited Investigate WAF, application, DNS, proxy, and other logs
Prevent recurrence Speed up the next decision Dependency inventory, SBOM, update procedure, contact route

“We Don’t Know If We’re Using It” Is the Biggest Source of Delay

In the question, even when Company G asks Company F whether it uses library H, the answer takes time because a detailed configuration analysis is needed.

In practice, if you only start searching for JAR files after a critical vulnerability is disclosed, your response is delayed. You should have at least the following in place during peacetime.

  • An inventory of direct and transitive dependencies.
  • The components and versions actually included in your artefacts.
  • Which services, containers, and devices they are deployed to.
  • A procedure for updating a dependent library and rebuilding/redistributing.
  • A contact route for approving emergency changes.
  • The allowed destinations for outbound traffic, and the impact of blocking them.
  • Where logs are stored and how to search them.

An SBOM is not the goal in itself. It is an index for answering, in a short time, “which running systems does this vulnerability affect”.

Don’t Stop Investigating Once You’ve Updated

You may have already been attacked around the time the vulnerability was disclosed. Updating to a patched version stops future exploitation, but it does not erase credentials that were already compromised or a backdoor that was already planted.

For a Log4Shell-type vulnerability, investigate at least the following angles.

  • HTTP requests containing suspicious strings indicating JNDI or LDAP.
  • Communication from the application server out to external LDAP, RMI, or HTTP.
  • Unusual child processes being launched.
  • Creation of suspicious JARs, classes, scripts, or executables.
  • Access to cloud credentials or environment variables.
  • Authentication, permission changes, and outbound transfers around the time of the update.

It matters not to conclude “we weren’t attacked” from WAF logs alone. There are internal paths that never pass through the WAF, and logs that were not retained in the past.

15. A Way of Reading That Makes Points Easier to Earn on the Exam

This question is less a knowledge test than an exercise in reading the gap between specification and implementation.

15.1 Separate “Specification” From “Implementation” in the Tables

In the status issue, a value absent from the API specification goes through in the implementation.

Specification:
  mid / name / age

Implementation:
  send all received parameters to P

Once you can see this gap, it becomes clear that blank c is common module P.

15.2 Draw a Line Under the Value the Attacker Changed

The value changed in each attack is as follows.

  • JWT header’s alg
  • JWT payload’s user ID
  • API parameter mid
  • Out-of-spec status
  • Authentication API’s otp
  • HTTP header x-api-version

Almost every question is asking “where should that value be verified”.

15.3 Bring the Answer Back to the Question Text’s Own Terms

In practice you can call these “BOLA”, “Mass Assignment”, and “rate limiting”. But what the question asks for is concrete processing matched to the structure of the question text.

A poor example:

Perform authorization appropriately.

A good example:

Verify whether the user ID contained in the JWT matches the value of mid.

A poor example:

Take brute-force countermeasures.

A good example:

Lock the account once the number of consecutive failures exceeds the threshold.

Knowing the abstract name alone does not produce an answer that can be marked within the character limit.

15.4 For the WAF, Trace “Where It Was Put”

The WAF’s inspection target is decided not by guessing from the attack type, but from where the attack string was placed.

Put into the x-api-version header
        ↓
Inspection target is Header

The grading commentary’s note that Question 3(1)’s correct-answer rate was somewhat low is also because many answers did not match the attack flow in Figure 6. Simply redrawing the attack sequence with arrows already reveals what should be observed.

16. A Checklist for Real-World API Reviews

A checklist for carrying this question back into actual design and code reviews.

JWT Verification

  • The allowed signature algorithm is fixed in server configuration.
  • none and unexpected algorithms are rejected.
  • Signature, iss, aud, exp, and nbf are verified as appropriate to the use case.
  • ID tokens, access tokens, and refresh tokens are not confused with each other.
  • There is a procedure for key rotation and revocation.
  • Information that must stay confidential is not put into the JWT payload.

Object-Level Authorization

  • Changing the ID inside a request cannot reach another user’s data.
  • Authorization is enforced across listing, detail, update, delete, and download alike.
  • Authorization is enforced in a common layer that reaches the data, not in the screen.
  • For self-only APIs, you considered deriving the target ID from the token instead.
  • Administrator operations use a separate policy from the general-user API.

Property-Level Authorization

  • The external input type and the database entity are kept separate.
  • Updatable fields are enumerated as an allowlist.
  • Out-of-spec properties are rejected or audited.
  • State such as permissions, billing, approval, and ownership cannot be changed from user input.
  • Responses also exclude unnecessary confidential properties.

Authentication Attempts

  • There is a per-account failure-count limit.
  • There is graduated delay and per-source control.
  • The failure count is not reset when the code is reissued.
  • The authentication code can only be used once.
  • Authentication codes and passwords are not left in logs.
  • The unlock/recovery procedure is not itself a weaker authentication route.

Critical Vulnerabilities in Dependent Libraries

  • Running services can be mapped to their dependency versions.
  • There is a procedure for verifying impact by a harmless method.
  • Interim measures such as WAF rules and outbound restrictions can be applied.
  • There is an operational process for a responsible person to review detection alerts.
  • There is an emergency release route for updating to a patched version.
  • Logs are investigated for possible exploitation before the update.

17. The Two Faces of Shared Components, as Seen Through This Question

This question features two shared components: JWT management library Q and common module P.

Shared components have major benefits.

  • Fixing one place propagates the fix to every API that uses it.
  • Authorization and verification logic don’t have to be duplicated into each feature.
  • Test targets can be consolidated.
  • Log and audit formats can be unified.

On the other hand, mistakes also spread across the whole system.

  • If library Q accepts alg=none, every API that uses JWTs becomes vulnerable.
  • If common module P accepts an arbitrary mid or status, both GET and PUT become vulnerable.
  • If vulnerable library H is used in the foundation, every route that logs HTTP headers becomes part of the attack surface.

What should be shared, therefore, is not mere data access. It is necessary to share the security invariants themselves, and rigorously verify that shared component in isolation.

For example, make P’s contract as follows.

P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})

It is safer not to expose a low-level API like the following directly to ordinary callers.

P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)

The latter is needed only by a limited set of routes, such as administrative processing. Handing that low-level freedom out to every API results in a design that depends on every single caller using it correctly, every time.

18. Summary

The Spring 2024 (Reiwa 6) PM Question 1 is a question that reads apart the topics of API security one at a time.

Using a JWT does not mean authentication is secure. Letting the attacker choose the signature algorithm lets the user ID be rewritten.

A valid JWT signature does not mean authorization is correct. Trusting the request’s mid lets a legitimate user access someone else’s information.

Being able to update your own object does not mean every property may be changed. Auto-binding internal state such as status lets permissions or billing status be rewritten.

Having an expiry on the authentication code does not mean it resists brute-forcing. You need to calculate the candidate space and attempt speed, and limit the number of failures.

Putting a rule into the WAF does not mean the vulnerability is fixed. Detection and blocking only buy time; you confirm the impact and ultimately update the library.

Mapping vulnerabilities to countermeasuresMaps each vulnerability to its trust boundary and its countermeasureToken verificationObject-level authorizationProperty-level authorizationAuthentication attempt controlInput to executionJWT tamperingFix the allowed algorithmmid swapMatch against JWT subject/no mid neededstatus=paidMake the update DTO an allowlist4-digit code brute forceFailure limit/delayLog4Shell-type vulnerabilityLibrary update/WAF

Figure 15: Mapping vulnerabilities to countermeasures. Separate the fix by which boundary was broken.

One principle runs through this entire question.

Never let success at the previous check become a reason to skip the next trust boundary.

Earlier articles in this series cover the stored XSS vulnerability in the Autumn 2023 (Reiwa 5) PM Question 1 and the guest Wi-Fi data exfiltration in the Autumn 2023 (Reiwa 5) PM Question 2. For a view of what to check across a website as a whole, see also Using IPA’s “How to Secure Your Website” as a Checklist.

Final summaryShows that success at the previous check is never a reason to skip the next trust boundaryAuthentication succeedsJWT signature verificationObject-level authorizationProperty-level authorizationAttempt-rate limitInput-to-execution boundaryWAF/library update

Figure 16: Final summary. Trust boundaries are checked in stages, and none may be skipped.

References

  1. IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Question Booklet. The question text this article is based on. 

  2. IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Model Answers. The official model answer for each question. 

  3. IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Grading Commentary. An explanation of correct-answer rates and common mistakes. 

  4. NIST, SP 800-63B: Authentication and Authenticator Management. Sets out digit counts for short-term secrets, attempt-rate limits, the failure count on reissue, and not using email for out-of-band authentication, among other requirements.  2

  5. RFC Editor, RFC 7519: JSON Web Token (JWT). The specification for JWTs, including the Unsecured JWT and alg=none

  6. RFC Editor, RFC 8725: JSON Web Token Best Current Practices. The BCP that sets out fixing the set of allowed algorithms, and verifying the issuer, subject, and audience, among other practices. 

  7. OWASP, API1:2023 Broken Object Level Authorization. Explains the need to check authorization for every object ID the user specifies. 

  8. OWASP, API3:2023 Broken Object Property Level Authorization. Explains property-level authorization flaws, including Mass Assignment, and their countermeasures. 

  9. Apache Logging Services, Security. Explains the impact of CVE-2021-44228, code execution via JNDI and LDAP, and the fixed versions. 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Website Development

Because in member APIs and smartphone integrations, JWT verification, object-level authorization, and restricting which properties can be updated map directly onto the security of the web system itself.

Technical Consulting & Design Review

Because identifying authorization gaps in existing APIs, assessing the blast radius of dependent libraries, and working out interim WAF rules through a design review all fall within the scope of technical consulting.

Frequently Asked Questions

Common questions about the topic of this article.

Why can an attacker impersonate someone else even though JWT signature verification succeeded?
In this question, the JWT management library accepted the JWT header's alg exactly as the attacker specified it, treating an alg=none JWT as valid with no signature at all. So even rewriting the payload's user ID still passes verification. The exam's answer is to verify the JWT header's alg and confirm that its value is not NONE. In practice, though, rejecting only NONE is not enough. Fix the algorithms allowed for use — for example, to RS256 — in server-side configuration, so the algorithm the token declares is never used directly to make the choice. You should also verify the issuer, audience, expiry, subject, and so on, as appropriate to the use case.
Is comparing the user ID inside the JWT against the request's mid enough as an authorization countermeasure?
For the purposes of this exam's answer, it is enough. Verifying, inside common module P, that the user ID contained in the JWT matches mid stops an attack that specifies someone else's mid. However, for an API that only handles the caller's own information, it is safer in practice not to accept mid from the client at all, and instead determine the user ID from the verified JWT's subject. Using something like GET /users/me or PUT /users/me makes it harder to end up with the comparison logic simply missing. An API where an administrator operates on another user should be split into a separate endpoint with its own authorization policy.
Why can't ordinary input validation alone stop the attack that adds status?
Because even if you validate the length of name or the range of age, that does nothing if status — a field that should never have been accepted in the first place — is auto-bound and passed straight to the internal object. The issue is not the format of a value; it's property-level authorization, i.e. whether the user is allowed to change that property at all. Define only name and age in the update input type, and reject unknown properties. Billing status must be changed only from server-trusted events, such as a successful result from the payment service.
The four-digit authentication code expires after 10 minutes — why is it still dangerous?
Because there are only 10,000 candidates from 0000 to 9999, and at 10 attempts per second an attacker succeeds after an average of 5,000 attempts — 500 seconds. The 10-minute validity period is 600 seconds, so trying non-repeating candidates in order lets an attacker check 6,000 of them within that window. Expiry time alone cannot stop brute-forcing. The candidate space, attempt speed, and attempt-count cap all need to be designed together.
The exam's countermeasure is account locking — is immediate locking on its own enough in practice too?
No. The blank in the question calls for logic that locks the account once the number of consecutive failures exceeds a threshold, but a fixed, permanent lock on its own lets an attacker deliberately lock someone else's account as a denial-of-service. In practice, combine a per-account failure count with graduated wait times, risk assessment of the source and device, notifications, and a recovery procedure. It also matters that issuing a new code does not reset the failure count back to zero.
What is the point of setting the WAF to detect rather than block?
It means legitimate business traffic doesn't get stopped even when a normal string is mistakenly judged an attack. In the model answer, the benefit is that it can prevent blocking caused by a false positive, and what should be done is to examine whether it's an attack whenever an alert is received. Detect mode is not a setting you leave alone. It is used as an observation period during which you check logs, filter out false positives, tune the rule, and then move to blocking. In an emergency where a known critical vulnerability is actually being exploited, it can be reasonable to weigh that against availability risk and choose to block from the start instead.
Is library H in this question Log4j?
The question withholds the product name, but the attack sequence — JNDI Lookup, an LDAP server, class retrieval from an HTTP server, a string embedded in an HTTP header, and a high CVSS v3.1 base score — reads naturally as an abstraction of CVE-2021-44228, known as Log4Shell. This article explains that correspondence, but the exam does not require you to name the specific product. It can be answered purely from the given attack procedure and the WAF specification.
What should you take back into practice from this question?
That succeeding at authentication, the JWT not having been tampered with, being allowed to access the target object, and being allowed to change the target property are all separate checks. On top of that, a short authentication code needs an attempt-rate limit, and for a critical library vulnerability you run impact confirmation, interim defence, and the root fix in parallel. The core practical takeaways are: consolidate authorization into shared components, make the input schema an allowlist, fix the JWT's verification conditions on the server side, and keep track of dependent libraries so you can update them.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog