블로그로 돌아가기

한 번만 채우기: curl·컬렉션·probe에 같은 --pvalue

curl, Postman, OpenAPI, status-code probe, Burp/ZAP replay에 그대로 흘려보내는 파라미터 placeholder 한 세트.

noir scan ./app -f curl을 돌리면 /users/{id}?limit= 벽이 나온다. 지도로는 쓸 만하다. 요청으로는 못 쓴다.

-f postman은 같은 빈칸을 다른 봉투에 넣은 것이고, --probe는 리터럴 {id}에 404를 맞거나 GET/HEAD/OPTIONS에 한해 1 / noir를 슬쩍 넣는다. sink 셋, 같은 파라미터에 대한 의견 셋.

--pvalue로 한 번에 정리한다. placeholder를 선언하면 optimizer가 포맷터·deliverer보다 먼저 모든 param에 값을 심는다. 스캔 한 번. 값 동일. sink 전부.

플래그

반복 가능. 각 인자는 TYPE=VALUE, 타입을 뺀 bare VALUE는 모든 타입에 먹힌다.

noir scan ./app -f curl -u https://api.example.com \
  --pvalue "path=id=42" \
  --pvalue "query=limit=10" \
  --pvalue "header=Authorization=Bearer replace-me"
TYPE 범위
any (또는 타입 생략) 모든 파라미터 타입
query / form / json / header / cookie / path 해당 버킷만

VALUE는 두 가지다.

형태 동작
42 대상 타입의 모든 파라미터
id=42 또는 id:42 이름이 id인 파라미터만

엔드포인트 목록을 정리하는 시점에 돌아간다. curl / HTTPie / PowerShell / OpenAPI / Postman / JSON / YAML과 probe/export보다 앞이다. 선언 하나, consumer 전부. 포맷: cURL / HTTPie / PowerShell. probe: 결과 전달.

순서가 발목을 잡는다

매칭은 first-hit. 타입 전용 규칙이 전역 any보다 먼저라서, path에서는 --pvalue path=…가 bare --pvalue test를 이긴다.

같은 타입 안에서는 catch-all이 모든 이름에 걸린다. 이름 지정 오버라이드를 앞에 둬라.

# limit → 10, 나머지 query → 1
noir scan ./app -f curl -u https://api.example.com \
  --pvalue "query=limit=10" \
  --pvalue "query=1"

두 줄을 뒤집으면 limit도 1이다. catch-all이 먼저 맞았기 때문. catch-all을 앞에 둔 문서 예시는 이 matcher 기준으로는 틀린 순서다. 이름 지정 → 타입 기본값.

같은 선언, 세 곳에서 이득 보는 지점

바로 붙여 넣을 수 있는 curl

noir scan ./api -f curl -u https://staging.example.com \
  --pvalue "path=id=42" \
  --pvalue "path=slug=demo" \
  --pvalue "query=1" \
  --pvalue "header=Authorization=Bearer $STAGING_TOKEN" \
  -o /tmp/api.curl.sh

source하면 끝. path 템플릿은 curl 빌더가 내보내는 URL에 이미 치환돼 있다. {id}를 줄마다 고칠 일 없다.

팀용 Postman / OpenAPI

noir scan ./api -f postman -u https://staging.example.com \
  --pvalue "path=id=42" \
  --pvalue "query=limit=10" \
  --pvalue "header=Authorization=Bearer replace-me" \
  -o /tmp/api.postman.json

noir scan ./api -f oas3 -u https://staging.example.com \
  --pvalue "path=id=42" \
  --pvalue "query=limit=10" \
  -o /tmp/api.openapi.json

curl 때와 같은 --pvalue 블록. 리뷰어는 컬렉션 하나 import, CI는 어제 OpenAPI랑 diff. 포맷이 바뀌어도 placeholder는 안 흔들린다.

status code로 라이브 검증, 필요하면 proxy replay

# 관측된 코드 붙이고, 뻔한 miss는 버림
noir scan ./api -u https://staging.example.com \
  --status-codes --exclude-codes 404,502 \
  --pvalue "path=id=42" \
  --pvalue "query=1" \
  -f json -o /tmp/live.json

# 타깃 부하를 두 배로 안 늘리고 Burp/ZAP로 read 트래픽만
noir scan ./api -u https://staging.example.com \
  --probe-via http://127.0.0.1:8080 \
  --probe-match "GET" \
  --pvalue "path=id=42" \
  --pvalue "header=Authorization=Bearer $STAGING_TOKEN"

--status-codes, --probe, --probe-via 전부 -u가 필요하다.

probe path 채움엔 나중에 뼈아픈 안전장치가 있다. --pvalue path=…가 없으면 read-only(GET, HEAD, OPTIONS)에만 1 / noir를 자동으로 넣는다. POST / PUT / PATCH / DELETE는 리터럴 템플릿을 유지해서, 실수로 DELETE /users/1이 나가지 않는다. 명시적 --pvalue path=id=42는 write를 포함한 모든 verb에 적용된다. 의도적으로 쓰거나, 준비될 때까지 --probe-skip "POST" / --probe-skip "DELETE"로 write를 막아 둬라.

기본값은 설정 파일에

같은 키, CI마다 플래그 붙여 넣기 없음:

url: "https://staging.example.com"
format: "json"
status_codes: true
exclude_codes: "404,502"
set_pvalue_path:
  - "id=42"
  - "slug=demo"
set_pvalue_query:
  - "limit=10"
  - "1"
set_pvalue_header:
  - "X-Debug-Tenant=noir-ci"
noir config init
noir scan ./api --config-file ./ci/noir.yaml

일회성으로 다른 id가 필요하면 CLI --pvalue가 이긴다. 설정 파일 · noir config.

여기서 자주 밟는다

  • 이름 없는 param은 값을 못 받는다. 구멍은 알지만 이름을 모르는 analyzer 결과는 --pvalue 전에 빠진다. curl에 이상한 =42는 안 생긴다.
  • body는 json으로 들어간다. body로 기록된 param은 optimizer가 아는 버킷으로 정규화되니 --pvalue json=…가 닿는다. 새 스크립트에선 옛 --set-pvalue-*보다 v1 --pvalue TYPE=VAL을 써라.
  • 헤더는 -u를 따른다. --probe-header와 probe payload용 --pvalue header=…는 -u 타깃용이다. 소스에 absolute host가 이미 찍힌 엔드포인트는 그 host를 유지하고, -u용 probe 헤더는 빠진다. 직접 지정한 export 목적지는 헤더를 그대로 받는다.
  • 시크릿은 커밋하지 마라. CI에서 env로 펼쳐라 (--pvalue "header=Authorization=Bearer $TOKEN"). ci/noir.yaml에 토큰 넣지 말고.

정리

값 없는 discovery는 번지 없는 지도다. --pvalue로 한 번 매기고 curl, 컬렉션, OpenAPI, status-code 필터, proxy replay에 같은 번호를 써라. catch-all보다 이름 지정 오버라이드를 앞에. write verb는 의도적으로. 팀이 placeholder에 합의하면 설정 파일에 기본값을.